在 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>
当您单击第一个按钮时,这将导致第二个按钮的背景在红色和绿色之间切换:
根据我下面的代码,我希望能够在单击 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>
当您单击第一个按钮时,这将导致第二个按钮的背景在红色和绿色之间切换: