当属性更改时如何更改任何控件的外观?

发布于 2024-09-26 16:15:48 字数 160 浏览 4 评论 0原文

我想向用户突出显示所有已修改的内容,以便他们知道自己更改了什么或在幕后以编程方式更改了哪些内容。

我想使用样式将此逻辑应用到我的所有控件,但我不确定如何操作。我知道我需要创建一个触发器,但不确定到底要触发什么,或者如何获取对绑定属性的任何更改,以便知道它是否已更改。

谢谢。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

冷情妓 2024-10-03 16:15:48

也许最好的方法是为您的值编写一个包装类,该包装类实现 INotifyPropertyChanged 并公开一个 object 类型的读/写 Value 属性和一个布尔值ValueHasChanged 属性。然后,您可以在 Value setter 中轻松进行更改跟踪:

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

视图模型类不应该公开 stringDateTime 属性,而应该公开 < code>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"

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

亚希 2024-10-03 16:15:48

我仍在掌握 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>

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>
街道布景 2024-10-03 16:15:48

我建议阅读有关名为 INotifyPropertyChanged 的​​设计模式的文章,

当您位于类框架上时,您不再位于 UI 线程中,因此您应该使用与 UI 线程相关的调度程序,看看这个

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>

因此当按下该按钮时,它将更改其内容的可见性。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文