Reputation: 17023
I'd like to highlight to the user anything that has been modified, so that they know what they have changed or what has been programatically changed behind the scenes for them.
I want to use styles to apply this logic to all my controls, but i'm not sure how. I know I need to create a trigger but not sure what to trigger on exactly or how to pick up any changes to the bound property in order to know if it's changed or not.
thanks.
Upvotes: 1
Views: 331
Reputation: 53709
I am still getting the hang of WPF, so there might be a much better approach, but here is what I thought could work.
Create a ValueTracker
class, this class will provide the following 3 Attached Dependency Properties
TrackProperty - This will be the property of the control that should be tracked
DefaultValue - This is the value that is considered the default, and thing else would trigger the Style trigger.
IsDefaultValue - This will indicate if the current value matches the default, this property will be used in the trigger test.
Here is a quick test, it is not perfect, but I am sure a little tweeking will get things going nicely and of course someone with more WPF experience could improve on this idea.
using System;
using System.Windows;
using System.ComponentModel;
namespace WpfApplication1
{
public static class ValueTracker
{
// Attached dependency property for DefaultValue
public static object GetDefaultValue(DependencyObject obj)
{
return (object)obj.GetValue(DefaultValueProperty);
}
public static void SetDefaultValue(DependencyObject obj, object value)
{
obj.SetValue(DefaultValueProperty, value);
}
public static readonly DependencyProperty DefaultValueProperty =
DependencyProperty.RegisterAttached("DefaultValue",
typeof(object), typeof(ValueTracker), new UIPropertyMetadata(0));
// Attached dependency property for IsDefaultValue
public static bool GetIsDefaultValue(DependencyObject obj)
{
return (bool)obj.GetValue(IsDefaultValueProperty);
}
private static void SetIsDefaultValue(DependencyObject obj, bool value)
{
obj.SetValue(IsDefaultValueProperty, value);
}
public static readonly DependencyProperty IsDefaultValueProperty =
DependencyProperty.RegisterAttached("IsDefaultValue",
typeof(bool), typeof(ValueTracker), new UIPropertyMetadata(false));
// Attached dependency property for TrackedProperty
public static DependencyProperty GetTrackProperty(DependencyObject obj)
{
return (DependencyProperty)obj.GetValue(TrackPropertyProperty);
}
public static void SetTrackProperty(DependencyObject obj, DependencyProperty value)
{
obj.SetValue(TrackPropertyProperty, value);
}
public static readonly DependencyProperty TrackPropertyProperty =
DependencyProperty.RegisterAttached("TrackProperty",
typeof(DependencyProperty), typeof(ValueTracker),
new UIPropertyMetadata(TrackPropertyChanged));
public static void TrackPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DependencyProperty oldProperty = e.OldValue as DependencyProperty;
if (oldProperty != null)
{
DependencyPropertyDescriptor dpd =
DependencyPropertyDescriptor.FromProperty(oldProperty, typeof(UIElement));
if (dpd != null)
{
dpd.RemoveValueChanged(d, TrackedPropertyValueChanged);
}
}
DependencyProperty newProperty = e.NewValue as DependencyProperty;
if (newProperty != null)
{
DependencyPropertyDescriptor dpd =
DependencyPropertyDescriptor.FromProperty(newProperty, typeof(UIElement));
if (dpd != null)
{
dpd.AddValueChanged(d, TrackedPropertyValueChanged);
}
}
}
private static void TrackedPropertyValueChanged(object sender, EventArgs e)
{
DependencyObject o = sender as DependencyObject;
if (o != null)
{
object defaultValue = Convert.ChangeType(GetDefaultValue(o), GetTrackProperty(o).PropertyType);
SetIsDefaultValue(o, Object.Equals(o.GetValue(GetTrackProperty(o)), defaultValue));
}
}
}
}
The above can be used as follows.
1- Create a style which triggers on the ValueTracker.IsDefaultValue
2- For each control that you want to track, you attach the style and set the ValueTracker.DefaultValue and set the property that should be tracked using the ValueTracker.TrackProperty.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:r="clr-namespace:WpfApplication1"
mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow"
d:DesignHeight="221" d:DesignWidth="287"
Width="250" Height="250">
<StackPanel Loaded="StackPanel_Loaded" >
<StackPanel.Resources>
<Style TargetType="{x:Type Control}" x:Key="trackChanges">
<Style.Triggers>
<Trigger Property="local:ValueTracker.IsDefaultValue" Value="false">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBox Name="textbox1" Width="100" Height="23"
local:ValueTracker.DefaultValue="Help"
local:ValueTracker.TrackProperty="TextBox.Text"
Style="{StaticResource ResourceKey=trackChanges}" />
<ComboBox Name="combobox1"
SelectedIndex="2"
local:ValueTracker.DefaultValue="2"
local:ValueTracker.TrackProperty="ComboBox.SelectedIndex"
Style="{StaticResource ResourceKey=trackChanges}">
<ComboBox.Items>
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
<ComboBoxItem>Item 3</ComboBoxItem>
<ComboBoxItem>Item 4</ComboBoxItem>
</ComboBox.Items>
</ComboBox>
</StackPanel>
</Window>
Upvotes: 2
Reputation: 1534
i would recomed reading about the design pattern called INotifyPropertyChanged
another thing when you are on a class frame you are no longer in the UI thread therefore you should use the dispatcher who is assoicated with the UI thread look at this
let's say you want to print new board you should be doing it like this
printDelgate paintControlDelgate = () => paintControl();
m_CurrentDispatcher.Invoke(paintControlDelgate);
let's assume for a minute you have a button in your code
<ControlTemplate x:Key="StarTemplate" TargetType="{x:Type Button}">
<Grid>
<ed:RegularPolygon Visibility="Collapsed" Name="star1" Fill="{Binding Path=ButtonColor}"
InnerRadius="0.47211" Margin="20.5,16,15.5,8" PointCount="5" Stroke="Black"
StrokeThickness="2" Height="40" Width="40"/>
<ed:RegularPolygon Name="star2" Fill="Black" Visibility="Visible" InnerRadius="0.47211" Margin="20.5,16,15.5,8"
PointCount="5" Stroke="Black" StrokeThickness="6" Height="40" Width="40"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="star1" Property="Visibility" Value="Visible"/>
<Setter TargetName="star2" Property="Visibility" Value="Collapsed"/>
</Trigger>
so when the button is pressed it will change it's content Visibility.
Upvotes: -1
Reputation: 96830
Probably the best approach involves writing a wrapper class for your values that implements INotifyPropertyChanged
and exposes a read/write Value
property of type object
and a boolean ValueHasChanged
property. You can then do change tracking pretty easily in the Value
setter:
if (!value.Equals(_Value))
{
_Value = value;
ValueHasChanged=true;
OnPropertyChanged("Value");
}
Instead of your view model class exposing string
or DateTime
properties, it should expose ValueWrapper
properties that wrap its internal fields, e.g.:
private string SomeStringField;
private ValueWrapper _SomeStringProperty;
public ValueWrapper SomeStringProperty
{
get
{
return (_SomeStringProperty == null)
? _SomeStringProperty = new ValueWrapper(SomeStringField)
: _SomeStringProperty;
}
}
Then you can build a style like:
<Style x:Key="ShowChangedValue">
<Setter Property="Background" Value="White"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ValueHasChanged}" Value="True">
<Setter Property="Background" Value="AliceBlue"/>
</DataTrigger>
</Style.Triggers>
</Style>
and use it like:
<TextBox DataContext="{Binding SomeStringProperty}"
Text="{Binding Value, Mode=TwoWay}"
Style="{StaticResource ShowChangedValue}"/>
There are some annoying things about this, like the fact that to change a property's value inside your main view model class you now have to use SomeStringProperty.Value = "foo"
instead of SomeStringProperty = "foo"
.
Upvotes: 2