在 WPF 应用程序中使用 MVVM/MVVMLight 时如何与 UI 元素交互

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

根据我下面的代码,我希望能够在单击 Button 1 时更改 Button 2 的背景颜色。

XAML 文件

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

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

视图模型文件

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;
        });
    }
}

spring 想到了两种方法 - 第一种是简单地将 Button2 的背景色绑定到视图模型上的 属性。您可以将其作为画笔从视图模型中公开;尽管与 MVVM 更一致的方式是创建一个值转换器。

想法是 Button2 的背景尽管链接到 Button1,但实际上链接到按下 Button1 时已更改的状态;值转换器然后将状态(即 ViewModel 的域)映射到颜色(视图的域)。

这样做,意味着可以在button1的视图模型命令中改变状态,而不必涉及button1_click事件,因为现在不需要了。

This question 说明了如何实现这一目标。

除了 pm_2 提到的之外,您还可以利用 MVVMLight 的 Messenger class。 VM 可以发送由 View 接收的消息来更改背景。

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

然后在您的虚拟机中:

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

....

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

在您看来:

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);
    }

}

另一种选择是让视图注册到它的 ViewModel 中 "view service"。例如:

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

在虚拟机中:

public IMySpecificViewService ViewService { get; set; } 

并在视图中

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;
}  

可以在您的 VM 的命令处理程序中调用:

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

我发现当我不能直接绑定到 VM 中的 属性 时,我会使用这些方法,(或者我不想在 VM 中释放任何 View 特定的东西)并且我需要更多细粒度控制操纵控件。

首先,您需要在视图模型中声明一个 属性 来控制背景颜色,以及一个按钮可以调用以切换它的命令处理程序。这可能看起来有点冗长,但您很快就会习惯使用 MVVM,并且如果它真的困扰您,您可以使用一些框架来尽量减少它。所以这是主视图模型:

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()
    {
    }

}

MVVM 的目标之一是使视图和视图模型之间的耦合尽可能松散。 Button 的命令绑定应该相当简单,但要设置第二个按钮的背景,您可以使用 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>

当您单击第一个按钮时,这将导致第二个按钮的背景在红色和绿色之间切换: