【问题标题】:WPF force xaml to re-fetch the property's value although the property's setter didn't changeWPF 强制 xaml 重新获取属性的值,尽管属性的设置器没有改变
【发布时间】:2020-09-23 10:38:33
【问题描述】:

我正在修改一个现有的 WPF 项目(我对 WPF 没有太多经验),并且我有这个属性:

public Point WidgetMiddlePoint
    {
        get
        {
            return new PointByAppMonitorDPI(_middlePoint);
            //return _middlePoint;
        }
    }

在 UI 方面:

<controls1:BorderWithTip.TipOffset>
    <MultiBinding Converter="{StaticResource TipOffsetPositionConverter}">
        <Binding Path="WidgetMiddlePoint" Delay="500"  NotifyOnSourceUpdated="False" NotifyOnTargetUpdated="False"/>
        <Binding ElementName="BorderWithTip" Path="ActualWidth" Delay="500" NotifyOnSourceUpdated="False" NotifyOnTargetUpdated="False"/>
    </MultiBinding>
</controls1:BorderWithTip.TipOffset>

TipOffsetPositionConverter 根据给定的参数执行一些计算。
我的问题是 WidgetMiddlePoint 值取决于应用程序所在的监视器的 DPI(DPI 与我的问题无关,它只是一个仅在调用 getter 时才考虑的因素的用例)。
所以发生的情况是 UI 从 getter 获取值并且不会刷新该值,除非我使用 setter 将其设置为其他值,然后“通知”。

如何配置 UI 以每次重新获取值,即使它“认为”属性的值没有改变?或者这是不好的做法,不推荐?

【问题讨论】:

  • 什么是“每次”?如果值发生变化,为什么不使用 setter 并通知?如果它没有改变,那么就不需要更新。您的要求不清楚。
  • 当一个属性的值没有变化时,为什么要向UI提示?
  • 我不想使用设置器,因为不应修改“原始”值。当应用程序被拖动到具有不同 DPI 设置的不同监视器时,getter 应根据“原始”值和新监视器的 DPI 返回不同的值。目前,我没有迹象表明 DPI 发生了变化。我可以以某种方式实现 DPI 通知程序,也可以弄清楚 UI 如何每次都获取属性的值。 setter 只会更改一次属性。 getter 将根据当前监视器的 DPI 返回不同的值。希望我的解释清楚......
  • INotifyPropertyChanged 允许您执行此操作。您需要以编程方式为您希望 UI 读取的属性引发 PropertyChanged 事件。

标签: c# wpf inotifypropertychanged


【解决方案1】:

您可以使用 Window DpiChanged 事件以及 INotifyPropertyChanged。

下面的代码显示了如何做到这一点。它有一个 RecalculateMiddlePoint 方法,可以创建一个测试点,该测试点具有 X 和 Y 值的当前 DPI,但显然它应该进行适当的计算。

如果您创建一个 WPF 应用程序,下面的代码会将中间点绑定到一个标签,因此当您在屏幕之间拖动主窗口时,它会在主窗口上显示不断变化的 DPI。该代码适用于 .NET Framework 4.8 和 .NET Core 3.1。

C#

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        // Hook the DpiChanged event
        this.DpiChanged += Window_DpiChanged;
        // Initialize our bound property
        WidgetMiddlePoint = RecalculateMiddlePoint(VisualTreeHelper.GetDpi(this));
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void Window_DpiChanged(object sender, DpiChangedEventArgs e)
    {
        Debug.WriteLine($"Old Scale: {e.OldDpi.DpiScaleX} New Scale: {e.NewDpi.DpiScaleX} " + 
                        $"Old PPI: {e.OldDpi.PixelsPerInchX} New PPI: {e.NewDpi.PixelsPerInchX}");
        // Recalculate _widgetMiddlePoint based on the values above and just set it
        WidgetMiddlePoint = RecalculateMiddlePoint(e.NewDpi);
    }

    private Point RecalculateMiddlePoint(DpiScale newDpi)
    {
        // Recalculate based on the new DPI in here
        // For testing we just create a 'Point' that has the PPI for X and Y values
        return new Point(newDpi.PixelsPerInchX, newDpi.PixelsPerInchX);
        //return new PointByAppMonitorDPI(_middlePoint);  // Correct code????
    }

    private Point _middlePoint;
    public Point WidgetMiddlePoint
    {
        get { return _middlePoint; }
        set
        {
            _middlePoint = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(WidgetMiddlePoint)));
        }
    }
}

XAML

<Window x:Class="WpfApp9.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp9"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Label Content="{Binding Path=WidgetMiddlePoint}" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" Width="120"/>
    </Grid>
</Window>

添加到 app.manifest:

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> PerMonitor</dpiAwareness>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
  </application>

【讨论】:

    【解决方案2】:

    如果您希望框架调用您的 getter,进而调用您的转换器,您应该实现 INotifyPropertyChanged 并在您的视图模型中引发 PropertyChanged 事件。

    您需要以某种方式确定 DPI 何时更改,然后引发事件。 Convert 方法仅在通知框架任何数据绑定属性(在本例中为WidgetMiddlePointActualWidth)发生更改时才被调用。

    【讨论】:

      【解决方案3】:

      Fody 有一个 PropertyChanged 插件

      https://github.com/Fody/PropertyChanged 它消除了使用 INotifyPropertyChanged 的​​一些样板

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-07
        • 2017-01-06
        • 1970-01-01
        • 2018-04-11
        • 2010-11-19
        • 2012-10-20
        相关资源
        最近更新 更多