Как изменить внешний вид любого элемента управления при изменении свойства? - PullRequest
1 голос
/ 07 октября 2010

Я хотел бы выделить пользователю все, что было изменено, чтобы они знали, что они изменили или что было программно изменено за кулисами для них.

Я хочу использовать стили, чтобы применить эту логику ко всем моим элементам управления, но я не уверен, как это сделать. Я знаю, что мне нужно создать триггер, но не уверен, что именно нужно запускать или как получить любые изменения в связанном свойстве, чтобы узнать, изменилось ли оно или нет.

спасибо.

Ответы [ 3 ]

2 голосов
/ 07 октября 2010

Вероятно, лучший подход предполагает написание класса-оболочки для ваших значений, который реализует INotifyPropertyChanged и предоставляет свойство чтения / записи Value типа object и логическое свойство ValueHasChanged.Затем вы можете довольно легко выполнить отслеживание изменений в установщике Value:

if (!value.Equals(_Value))
{
   _Value = value;
   ValueHasChanged=true;
   OnPropertyChanged("Value");
}

Вместо того, чтобы ваш класс модели представления выставлял свойства string или DateTime, он должен предоставлять свойства ValueWrapper, которые обертывают еговнутренние поля, например:

private string SomeStringField;

private ValueWrapper _SomeStringProperty;

public ValueWrapper SomeStringProperty
{
   get 
   { 
      return (_SomeStringProperty == null) 
         ? _SomeStringProperty = new ValueWrapper(SomeStringField) 
         : _SomeStringProperty; 
   }
}

Затем вы можете создать стиль вроде:

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

и использовать его следующим образом:

<TextBox DataContext="{Binding SomeStringProperty}" 
         Text="{Binding Value, Mode=TwoWay}"
         Style="{StaticResource ShowChangedValue}"/>

Есть некоторые раздражающие вещиоб этом, например, о том, что для изменения значения свойства в классе модели основного представления теперь необходимо использовать SomeStringProperty.Value = "foo" вместо SomeStringProperty = "foo".

2 голосов
/ 07 октября 2010

Я все еще изучаю WPF, так что, возможно, подход гораздо лучше, но я подумал, что это может сработать.

Создайте ValueTracker класс, этот класс обеспечит следующие 3 свойства присоединенной зависимости

TrackProperty - Это будет свойство элемента управления, который должен отслеживаться

DefaultValue - это значение, которое считается значением по умолчанию, и что-то еще может вызвать триггер стиля.

IsDefaultValue - Это будет указывать, если текущее значение соответствует значению по умолчанию, это свойство будет использоваться в тесте триггера.

Это быстрый тест, но он не идеален, но я уверен, что небольшой твиктинг даст хорошие результаты, и, конечно, кто-то с большим опытом работы с WPF может улучшить эту идею.

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));        
      }
    }
  }
}

Вышеуказанное можно использовать следующим образом.

1 - Создать стиль, который запускается в ValueTracker.IsDefaultValue

2- Для каждого элемента управления, который вы хотите отслеживать, вы присоединяете стиль и задаете ValueTracker.DefaultValue и задаете свойство, которое должно отслеживаться с помощью 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>
0 голосов
/ 07 октября 2010

я бы рекомендовал прочесть о шаблоне проектирования INotifyPropertyChanged

Еще одна вещь, когда вы находитесь в фрейме класса, вы больше не находитесь в потоке пользовательского интерфейса, поэтому вам следует использовать диспетчер, который связан с внешним видом потока пользовательского интерфейсав этом

диспетчере на MSDN

скажем, вы хотите напечатать новую доску, вы должны делать это так

  printDelgate paintControlDelgate = () => paintControl();
  m_CurrentDispatcher.Invoke(paintControlDelgate);

предположим, что дляминуту у вас есть кнопка в вашем коде

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

, поэтому при нажатии кнопки она изменит свой контент. Видимость.

...