动态更新多个窗口/用户控件的样式 - WPF






4.25/5 (4投票s)
描述了一种基于属性设置动态更新多个窗口样式的技术。该技术使用数据触发器。通过创建支持日间和夜间模式的两个窗口来演示该技术。
引言
本文的目的是演示如何通过更改单个属性值来一次性更新多个窗口的样式。它通过使用数据触发器和一个抽象的基视图模型来实现。对于那些希望通过仅更新一个属性来对多个窗口/控件进行样式更改的人来说,本文可能会有所帮助。整个概念通过创建实现日间和夜间模式的两个窗口来演示。此概念可用于许多其他实现,例如创建需要更新另一个窗口的屏幕键盘。
背景
本文使用了 MVVM (Model View View Model) 框架。它还使用 Autofac 的 IoC 容器和一个路由命令。对这些概念有基本的了解将会有所帮助。
属性类
UI 能够根据不同的配置和属性设置来更改外观。有一个包含不同属性的 `AppInfo` 类。WPF 窗口上的组件间接绑定到这些属性(意味着 – WPF 窗口绑定到其各自视图模型类中的一个属性,并且该属性通过注册到从 `AppInfo` 类引发的 `DayNightModeChanged` 事件来更新)。这允许 WPF 页面在属性更改时动态更新。这是通过在 XAML 代码中使用 `DataTrigger`s 来完成的。
我将使用日/夜模式功能来演示这个概念。
目标:当 `AppInfo` 类中的布尔属性 `DayMode` 更改时,UI 应更改其外观。
以下类包含我们希望监视的属性 (`DayMode`)。
public class AppInfo
{
private bool _dayMode = true;
public bool DayMode
{
get
{
return _dayMode;
}
set
{
_dayMode = value;
DayNightModeChanged(value);
}
}
// all windows that want to monitor
// the DayMode property need to subscribe to this action
public Action<bool> DayNightModeChanged;
}
设置 `DayMode` 属性时,我们引发 `DayNightModeChanged` 事件。
基类
所有视图模型(`Window1ViewModel` 和 `Window2ViewModel`)都实现一个抽象类 `ViewModelBase`。
public class HeaderViewModel : ViewModelBase
public class LoginViewModel : ViewModelBase
`ViewModelBase` 实现了 `INotifyPropertyChanged`,以便 WPF 页面知道 `DayMode` 属性何时发生更改。
public abstract class ViewModelBase : DependencyObject, INotifyPropertyChanged, IDisposable
在 `ViewModelBase` 类的构造函数中,我们注册 `DayNightModeChanged` 事件。
public ViewModelBase()
{
// without this condition the designer has trouble loading
// the window. Not necessary for run time,
// but is nice so that you can see your changes in the designer.
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
IocContainer.Resolve<AppInfo>().DayNightModeChanged +=
new Action<bool>(DayNightMode_Changed);
}
}
// this handles the action so that we can update the DayMode property in this class.
// the DayMode property in this class is the one that the windows are bound to.
private void DayNightMode_Changed(bool result)
{
DayMode = result;
}
当在 `ViewModelBase` 中设置 `DayMode` 时,我们引发 `OnPropertyChanged` 事件,并传入字符串“DayMode”。
public bool DayMode
{
get { return (bool)GetValue(DayModeProperty); }
set
{
SetValue(DayModeProperty, value);
OnPropertyChanged("DayMode");
}
}
// this is to make use of the UpdateSourceTrigger attribute in the xaml code.
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
鉴于所有视图都实现了 `ViewModelBase`,因此所有绑定到 `DayMode` 的视图都将接收到属性更改。以下演示了绑定。
绑定
我有一个名为 `Header` 的用户控件。`Header` 的 `DataContext` 设置为 `HeaderViewModel`(记住 `HeaderViewModel` 实现了 `ViewModelBase`)。在该分配期间,我们还分配 `DayMode` = true。*这仅在设计时使用,以便可以观察到外观的变化。*
<!--Design time data-->
<UserControl.DataContext>
<ViewModels:HeaderViewModel DayMode="True"></ViewModels:HeaderViewModel>
</UserControl.DataContext>
我使用 `DataTrigger`s 设置了 `Header` 中 `Grid` 的样式...
<Grid x:Name="myGrid">
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=DayMode, UpdateSourceTrigger=PropertyChanged}"
Value="false">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint=".5,0" EndPoint=".5, 1">
<GradientStop Color="Black" Offset="0.0" />
<GradientStop Color="#6F6F6F" Offset="0.95" />
<GradientStop Color="Black" Offset="1.0" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=DayMode,
UpdateSourceTrigger=PropertyChanged}" Value="true">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint=".5,0" EndPoint=".5, 1">
<GradientStop Color="#ECECEC" Offset="0.0" />
<GradientStop Color="#CCCCCC" Offset="0.95" />
<GradientStop Color="Black" Offset="1.0" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
请注意,有两个绑定到 `DayMode` 的 `DataTrigger`s。它们都通过 `PropertyChanged` 事件进行更新。一个检查 true,另一个检查 false。网格根据 `DayMode` 的值采用其各自 `DataTrigger`s 中的样式属性。
结论
通过以这种方式设置更多属性,我们可以用很少的代码动态更新 UI 的外观。