简体   繁体   English

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

[英]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. 根据我下面的代码,我希望能够在单击Button 1时更改Button 2的背景颜色。

XAML File XAML文件

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

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

ViewModel File ViewModel文件

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

In addition to what pm_2 mentioned, you could take advantage of MVVMLight's Messenger class. 除了pm_2提到的内容之外,您还可以利用MVVMLight的Messenger类。 The VM can send a message that is received by the View to change the background. VM可以发送视图接收的消息以更改背景。

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

And then in your VM: 然后在你的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. 另一种选择是拥有View查看其ViewModel的“视图服务”。 For example: 例如:

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

In VM: 在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: 可以在VM的命令处理程序中调用它:

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. 当我无法直接绑定到VM中的属性时,我发现我使用这些方法(或者我不想在VM中放出任何View特定的东西),并且我需要对操作控件进行更精细的控制。

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. 我想到了两种方法 - 第一种方法是简单地将Button2的背景颜色绑定到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. 虽然与MVVM更一致的方式是创建一个值转换器。

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; 想法是Button2的背景,尽管与Button1相关联,实际上是与按下Button1时已经改变的状态相关联; the value converter then maps the state (which is the domain of the ViewModel) with the colour (the domain of the view). 然后,值转换器将状态(ViewModel的域)映射为颜色(视图的域)。

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. 这样做意味着你可以在button1的视图模型命令中更改状态,但不必涉及button1_click事件,因为它现在是不必要的。

This question illustrates how you might achieve this. 这个问题说明了如何实现这一目标。

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. 这可能看起来有点冗长,但您很快就会习惯使用MVVM,并且有一些框架可以用来最小化它,如果它真的困扰你。 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. MVVM的目标之一是尽可能在视图和视图模型之间进行松散耦合。 The Button's command binding should be fairly straightforward, but to set the background of the second button you can use DataTriggers: 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>

This will cause the second button's background to toggle between red and green as you click the first button: 当您单击第一个按钮时,这将导致第二个按钮的背景在红色和绿色之间切换:

在此输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM