Introduction
The purpose of this article is to demonstrate how to update the style for multiple windows at once by changing a single property value. It does this by using data triggers and an abstract base view model. This article will probably be helpful for someone who wants to make style changes to multiple windows/controls by just updating one property. This whole concept is demonstrated by creating two windows that implement a day and night mode. This concept can be used for many other implementations, such as creating an onscreen keyboard that needs to update another window.
Background
This article makes use of the MVVM (Model View View Model) Framework. It also uses Autofac's IoC containers and a routed command. It will be helpful to have a basic understanding of these concepts.
The Property Class
The UI has the ability to change the appearance based on different configurations and property settings. There is a class AppInfo
that contains different properties. There are components on WPF windows that bind to these properties indirectly (meaning – the WPF window is bound to a property in its respective view model class, and the property gets updated by registering to a DayNightModeChanged
event that gets raised from the AppInfo
class). This allows the WPF pages to update dynamically whenever the property changes. This is done by using DataTrigger
s in the XAML code.
I will demonstrate this concept using the Day/Night mode functionality.
Goal: When the boolean property DayMode
in the AppInfo
class changes, the UI shall change its appearance.
The following class contains the property that we wish to monitor (DayMode
).
public class AppInfo
{
private bool _dayMode = true;
public bool DayMode
{
get
{
return _dayMode;
}
set
{
_dayMode = value;
DayNightModeChanged(value);
}
}
public Action<bool> DayNightModeChanged;
}
When the DayMode
property is set, we raise the event DayNightModeChanged
.
The Base Class
All View Models (Window1ViewModel
and Window2ViewModel
) implement an abstract class ViewModelBase
.
public class HeaderViewModel : ViewModelBase
public class LoginViewModel : ViewModelBase
ViewModelBase
implements INotifyPropertyChanged
so that the WPF pages will know when the DayMode
property has changed.
public abstract class ViewModelBase : DependencyObject, INotifyPropertyChanged, IDisposable
In the constructor of the ViewModelBase
class, we register for the DayNightModeChanged
event.
public ViewModelBase()
{
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
IocContainer.Resolve<AppInfo>().DayNightModeChanged +=
new Action<bool>(DayNightMode_Changed);
}
}
private void DayNightMode_Changed(bool result)
{
DayMode = result;
}
When DayMode
is set in ViewModelBase
, we raise the OnPropertyChanged
event, passing in the string “DayMode”.
public bool DayMode
{
get { return (bool)GetValue(DayModeProperty); }
set
{
SetValue(DayModeProperty, value);
OnPropertyChanged("DayMode");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
Given that all Views implement ViewModelBase
, all Views that bind to DayMode
will receive the property change. The following demonstrates the binding.
The Binding
I have a User Control named Header
. The DataContext
of Header
is set to HeaderViewModel
(remembering that HeaderViewModel
implements ViewModelBase
). During that assignment, we also assign DayMode
= true. This is used during design time only, so that the change in appearance can be observed.
<!---->
<UserControl.DataContext>
<ViewModels:HeaderViewModel DayMode="True"></ViewModels:HeaderViewModel>
</UserControl.DataContext>
I set up the style of the Grid
in my Header
using DataTrigger
s...
<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>
Notice how there are two DataTrigger
s bound to DayMode
. They both get updated by the PropertyChanged
event. One checks for true and one for false. The grid takes on the style properties in their respective DataTrigger
s depending on the value of DayMode
.
Conclusion
By setting up more properties in this way, we can dynamically update the appearance of the UI with very little code.