fs_tigre
fs_tigre

Reputation: 10738

How to interact with UI elements when using MVVM/MVVMLight in a WPF app

Based on my code below, I want to be able to change the background color of a Button 2 when Button 1 is clicked.

XAML File

    <Grid>
        <Button x:Name="Button1" 
                Content="Button 1" 
                Command="{Binding Button1Command}"/>

        <Button x:Name="Button2" 
                Content="Button 2"/>
    </Grid>

ViewModel File

public class MyViewModel : ViewModelBase
{
    public ICommand Button1Command{get;private set;}

    public MyViewModel(){
        Button1Command = new RelayCommand(() => button1_Click());
    }

    private void button1_Click()
    {
        Console.WriteLine("Button 1 clicked");

        // how can I change the background color of Button 2 here
        this.Dispatcher.Invoke(() => {
           Button2.Background = Brushes.Red;
        });
    }
}

Upvotes: 4

Views: 1507

Answers (3)

Mark Feldman
Mark Feldman

Reputation: 16119

First of all you need to declare a property in your view model that will control the background color as well as a command handler which a button can call to toggle it. This might seem a little verbose but you soon get used to that with MVVM, and there are frameworks you can use to minimize that if it really bothers you. So here's the main view model:

public class MainViewModel : ViewModelBase
{
    #region Background Color Flag

    private bool _Flag;
    public bool Flag
    {
        get { return this._Flag; }
        set
        {
            if (this._Flag != value)
            {
                this._Flag = value;
                RaisePropertyChanged(() => this.Flag);
            }
        }
    }

    #endregion Background Color Flag

    #region Button Command Handler

    private ICommand _ButtonCommand;
    public ICommand ButtonCommand
    {
        get { return this._ButtonCommand = (this._ButtonCommand ?? new RelayCommand(OnButtonPressed)); }
    }

    private void OnButtonPressed()
    {
        this.Flag = !this.Flag;
    }

    #endregion Button Command Handler

    public MainViewModel()
    {
    }

}

One of the objectives of MVVM is to have as loose coupling between the view and the view model as possible. The Button's command binding should be fairly straightforward, but to set the background of the second button you can use DataTriggers:

<StackPanel Orientation="Vertical">

    <Button Content="Toggle Background" HorizontalAlignment="Left" VerticalAlignment="Top"
        Command="{Binding ButtonCommand}" />

    <Button Content="Hello World!" HorizontalAlignment="Left" VerticalAlignment="Top">
        <Button.Style>
            <Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Flag}" Value="False">
                        <Setter Property="Background" Value="Red" />
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Flag}" Value="True">
                        <Setter Property="Background" Value="Green" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
    </Button>

</StackPanel>

This will cause the second button's background to toggle between red and green as you click the first button:

enter image description here

Upvotes: 1

flyte
flyte

Reputation: 1322

In addition to what pm_2 mentioned, you could take advantage of MVVMLight's Messenger class. The VM can send a message that is received by the View to change the background.

public class ChangeBackgroundMessage
{
    public Brush TheColor { get; set; } 
} 

And then in your VM:

Button1Command = new RelayCommand(() => ExecuteButtonCommand());

....

private void ExecuteButtonCommand()
{
    Messenger.Default.Send<ChangeBackgroundMessage>(new ChangeBackgroundMessage { TheColor = Brushes.Red } );
} 

and in your View:

public partial class MyView : UserControl
{
    public MyView()
    {
         InitializeComponent();
         Messenger.Default.Register<ChangeBackgroundMessage>(this, m => ReceiveChangeBackgroundMessage(m);
    } 

    private void ReceiveChangeBackgroundMessage(ChangeBackgroundMessage m)
    {
          // If you need to ensure this executes only on UI thread, use the
          // DispatcherHelper class

          DispatcherHelper.CheckBeginInvokeOnUI(() => button2.Background = m.TheColor);
    }

}

Yet another alternative would be to have a "view service" that the View registers with it's ViewModel. For example:

public interface IMySpecificViewService
{ 
    void ChangeButtonColor(Brush color);
} 

In VM:

public IMySpecificViewService ViewService { get; set; } 

and in View

public partial class MyView : UserControl, IMySpecificViewService
...
public MyView()
{ 
    var vm = (MyViewModel)this.DataContext;
    vm.ViewService = (IMySpecificViewService)this;
} 

public void ChangeButtonColor(Brush color)
{
    Button2.Background = color;
}  

which can be called in your VM's command handler:

private void ExecuteButtonCommand()
{
    ViewService?.ChangeButtonColor(Brushes.Red);
} 

I find I use these approaches when I can't directly bind to a property in the VM, (or I don't want to bleed any View specific stuff in the VM) and I need more fine grained control over manipulating the controls.

Upvotes: 4

Paul Michaels
Paul Michaels

Reputation: 16685

There are two approaches to this that spring to mind - the first is to simply bind the background colour of Button2 to a property on the viewmodel. You could expose this from the view model as a brush; although the way that is more consistent with MVVM would be to create a value converter.

The idea being that the background of Button2, despite being linked to Button1, is actually linked to a state that has changed when Button1 is pressed; the value converter then maps the state (which is the domain of the ViewModel) with the colour (the domain of the view).

Doing is this way, means that you can change the state in the view model command of button1, but not have to involve the button1_click event, as it is now unnecessary.

This question illustrates how you might achieve this.

Upvotes: 3

Related Questions