views:

62

answers:

3

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.

A: 

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

dispatcher on msdn

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.

yoav.str
+2  A: 

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".

Robert Rossney
+2  A: 

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>
Chris Taylor