Donnerstag, 17. Februar 2011

ValidationRule with Parameter using AttatchedProperty

Usually it's a good idea to use Converters and Valitators to handle the data exchange between user interface and data source.

But ValitationRules have a big disadvantage...there is no easy way to get some parameters inside. Well, static parameters aren't a problem, but I had to use Bindings for them. There are several work-arounds in the internet, like this one:

http://khason.net/blog/fully-binded-validation-by-using-dependency-and-attached-properties/

My problem is, that my parameters aren't DependencyProperties...they rely on a DataTable which is set to DataContextProperty.

Enogh talking, here's my solution:

First of all I have a ValidationRule like this:

  public class DateInRangeValidator : ValidationRule
  {
    public DateTime Min { get; set; }
    public DateTime Max { get; set; }

    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
      if (value != null
        && value is DateTime
        && (this.Min.CompareTo((DateTime)value) > 0
        | this.Max.CompareTo((DateTime)value) < 0))
      {
        return new ValidationResult(false, "Wrong Date!");
      }
      return new ValidationResult(true, "Everything is fine.");
    }
  }



With fix values for minimum and maximun a sample XAML-code looks like this:

  <Grid Name="grid1">
    <DatePicker Name="datepicker">
      <DatePicker.SelectedDate>
        <Binding Path="column1" UpdateSourceTrigger="PropertyChanged">
          <Binding.ValidationRules>
            <util:DateInRangeValidator Min="1.1.2008" Max="1.1.2020"/>
          </Binding.ValidationRules>
        </Binding>
      </DatePicker.SelectedDate>
    </DatePicker>
  </Grid>



with this code-behind:

  private void Window_Initialized(object sender, EventArgs e)
  {
    DataTable datatable = new DataTable
    {
      Columns =
      {
        new DataColumn("column1", typeof(DateTime)),
        new DataColumn("column2", typeof(DateTime)),
        new DataColumn("column3", typeof(DateTime))
      }
    };

    DataRow datarow = datatable.NewRow();
    datarow["column1"] = new DateTime(2010, 1, 1);
    datarow["column2"] = new DateTime(2005, 1, 1);
    datarow["column3"] = new DateTime(2020, 1, 1);
    datatable.Rows.Add(datarow);

    this.grid1.DataContext = datatable;
  }


But now we'd like to use Bindings to set the DateInRangeValidator.Min and DateInRangeValidator.Max values. Maybe the are used elsewhere and aren't static during runtime.

There are some approches like using an embedded DependencyObject in the ValidationRule, but those aren't part of the logical tree and cannot use DataContext (just static ressources). 


Also there are some approches like this one: http://michlg.wordpress.com/2010/01/29/wpf-custom-validationrule-with-an-additional-parameter/ using a work-around to simulate input and output-parameters.

Well...my idea is based on the first link on top of this post. I define a class based on DependencyObject:

  public class MinMaxRange : DependencyObject

Inside I define two attatched DependencyProperties: one for minimum:

  public static object GetMin(DependencyObject obj)
  {
    return (object)obj.GetValue(MinProperty);
  }
  public static void SetMin(DependencyObject obj, int value)
  {
    obj.SetValue(MinProperty, value);
  }
  public static readonly DependencyProperty MinProperty
    = DependencyProperty.RegisterAttached("Min",
        typeof(object),
        typeof(MinMaxRange),
        new UIPropertyMetadata(null, OnAttachedPropertyChanged));



and one for maximum:

  public static object GetMax(DependencyObject obj)
  {
    return (object)obj.GetValue(MaxProperty);
  }
  public static void SetMax(DependencyObject obj, int value)
  {
    obj.SetValue(MaxProperty, value);
  }
  public static readonly DependencyProperty MaxProperty
    = DependencyProperty.RegisterAttached("Max",
        typeof(object),
        typeof(MinMaxRange),
        new UIPropertyMetadata(null, OnAttachedPropertyChanged));



The OnAttatchedPropertyChanged-Event is the heart of this solution and will be discussed later. First I'd like to discribe my target: When you have this XAML-code:

  <Grid Name="grid1">
    <DatePicker Name="datepicker"
                util:MinMaxRange.Min="{Binding column2}"
                util:MinMaxRange.Max="{Binding column3}">
      <DatePicker.SelectedDate>
        <Binding Path="column1" UpdateSourceTrigger="PropertyChanged">
          <Binding.ValidationRules>
            <util:DateInRangeValidator/>
          </Binding.ValidationRules>
        </Binding>
      </DatePicker.SelectedDate>
    </DatePicker>
  </Grid>

the code-behind should look for each DependencyProperty with Binding. Each Binding with ValidationRule which has a Min- and Max-property should get the value of the attatched property.

Here is the code of OnAttatchedPropertyChanged (with some comments):

  static void OnAttachedPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  {
    // array of all fields which allow bindings:
    System.Reflection.FieldInfo[] properties
      = obj.GetType().GetFields(
            System.Reflection.BindingFlags.Public
          | System.Reflection.BindingFlags.Static
          | System.Reflection.BindingFlags.FlattenHierarchy);
    // filter array for DependencyProperties:
    List<DependencyProperty> dependencypropertieslist = new List<DependencyProperty>();
    foreach (System.Reflection.FieldInfo fieldinfo in properties)
      if (fieldinfo.FieldType == typeof(DependencyProperty))
        dependencypropertieslist.Add((DependencyProperty)fieldinfo.GetValue(null));
    // run through all DependencyProperties:
    foreach (DependencyProperty dp in dependencypropertieslist)
      // has DependencyProperty Binding?
      if (BindingOperations.IsDataBound(obj, dp))
      {
        // cast Binding:
        Binding binding = BindingOperations.GetBinding(obj, dp);
        if (binding != null)
        {
          // loop over all ValidationRules:
          foreach (ValidationRule validationrule in binding.ValidationRules)
          {
            // loop over all properties:
            foreach (System.Reflection.PropertyInfo pi in validationrule.GetType().GetProperties())
              if (pi.Name.Equals(e.Property.Name))
              {
                // assign value:
                pi.SetValue(validationrule, e.NewValue, null);
              }
          }
        }
      }
  }

Basicly when the attatched property Min or Max changes, this method looks for all ValidationRules with property Min or Max.

Please pay attention, that there is no safe casting of the value-type. Also every ValidationRule with those parameters will be set.



Have fun! And if there are any mistakes in this, let me know :-) 

Montag, 7. Februar 2011

Set Styles of ListViewItem or TreeViewItem with ItemTemplate

I just had to modify the appearance of Items in a ListView and TreeView which are generated during runtime using ItemsControl.ItemsSource and ItemsControl.ItemTemplate like this:

  <ListView Name="listview">
    <ListView.ItemTemplate>
      <DataTemplate>
        <TextBlock Text="{Binding}"/>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>

  List<string> lst = new List<string>
  {
    "item1",
    "item2",
    "item3"
  };
  this.listview.ItemsSource = lst;

Goal is to change the look of the items which are selected (the look is okay if the ItemsControl is focused, but if not the gray color is a bit odd). When dealing with ItemsControls you really should take a look into the blog by Dr. WPF.

But let's take a look into the basic Style of a ListViewItem:

    <Style x:Key="ListViewItemStyle1" TargetType="{x:Type ListViewItem}">
      <Setter Property="Background" Value="Transparent"/>
      <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
      <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
      <Setter Property="Padding" Value="2,0,0,0"/>
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type ListViewItem}">
            <Border x:Name="Bd" SnapsToDevicePixels="true" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
              <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            </Border>
            <ControlTemplate.Triggers>
              <Trigger Property="IsSelected" Value="true">
                <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
              </Trigger>
              <MultiTrigger>
                <MultiTrigger.Conditions>
                  <Condition Property="IsSelected" Value="true"/>
                  <Condition Property="Selector.IsSelectionActive" Value="false"/>
                </MultiTrigger.Conditions>
                <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
              </MultiTrigger>
              <Trigger Property="IsEnabled" Value="false">
                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
The marked lines set the apperaence when TreeViewItem.IsSelected is true.

Now the Style has to be assigned to each generated item. I'll do this by waiting for the ItemsControl.ItemContainerGenerator to finish generation of the container for each item (refer to Dr. WPF, he really get's the point):


Set the event, e.g. in Window.Initialized:

  private void Window_Initialized(object sender, EventArgs e)
  {
    this.listview.ItemContainerGenerator.StatusChanged
      += new EventHandler(ItemContainerGenerator_StatusChanged);
  }

And (after generation) take each container and assign the Style:

  void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
  {
    if (this.listview.ItemContainerGenerator.Status
      == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
      foreach (var item in this.listview.Items)
      {
        DependencyObject dependencyobject
          = this.listview.ItemContainerGenerator.ContainerFromItem(item);
        if (dependencyobject as ListViewItem != null
          && (dependencyobject as ListViewItem).Style == null)
          (dependencyobject as ListViewItem).Style
            = (Style)this.FindResource("ListViewItemStyle1");
      }
  }


That's it. If there's a better and/or more effective way please let me know :-)