简体   繁体   English

WPF 强制 xaml 重新获取属性的值,尽管属性的 setter 没有改变

[英]WPF force xaml to re-fetch the property's value although the property's setter didn't change

I'm modifying an existing WPF project at work (I don't have much experience with WPF), and I have this property:我正在修改一个现有的 WPF 项目(我对 WPF 没有太多经验),我有这个属性:

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

And this at the UI side:这在用户界面方面:

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

The TipOffsetPositionConverter performs some calculations based on the given parameters. TipOffsetPositionConverter 根据给定的参数执行一些计算。
My problem is that the WidgetMiddlePoint value depends on the DPI of the monitor in which the app resides (the DPI isn't relevant for my question, it's just a use case for a factor that is taken into account only when calling the getter).我的问题是 WidgetMiddlePoint 值取决于应用程序所在的监视器的 DPI(DPI 与我的问题无关,它只是一个用例,仅在调用 getter 时才考虑一个​​因素)。
So what happens is that the UI takes the value from the getter and won't refresh that value unless I use the setter to set it to something else, and then 'notify'.所以发生的情况是 UI 从 getter 获取值并且不会刷新该值,除非我使用 setter 将其设置为其他内容,然后“通知”。

How can I configure the UI to re-get the value every time, even when it 'thinks' that the property's value hasn't changed?我如何配置 UI 以每次重新获取值,即使它“认为”属性的值没有改变? Or is it bad practice and not recommended?或者这是不好的做法而不推荐?

If you want the the framework to call your getter, and consequently your converter, you should implement INotifyPropertyChanged and raise the PropertyChanged event in your view model.如果您希望框架调用您的 getter,从而调用您的转换器,您应该实现INotifyPropertyChanged并在您的视图模型中引发PropertyChanged事件。

You need to somehow determine when the DPI changes and then raise the event.您需要以某种方式确定 DPI 何时更改,然后引发事件。 The Convert method is only called whenever the framework is notified of a change of any of the data-bound properties ( WidgetMiddlePoint and ActualWidth in this case).只有当框架收到任何数据绑定属性(在本例中为WidgetMiddlePointActualWidth )更改的通知时,才会调用Convert方法。

There's a Window DpiChanged event you can use for this, along with INotifyPropertyChanged.您可以使用Window DpiChanged事件以及 INotifyPropertyChanged。

The code below shows how to do this.下面的代码显示了如何做到这一点。 It has a RecalculateMiddlePoint method that creates a test Point that has the current DPI for both X and Y values, but clearly it should do the appropriate calculation.它有一个 RecalculateMiddlePoint 方法,该方法创建一个测试点,该测试点具有 X 和 Y 值的当前 DPI,但显然它应该进行适当的计算。

If you create a WPF app, the code below binds the middle point to a label and hence shows the changing DPI on the main window as you drag it between screens.如果您创建 WPF 应用程序,下面的代码会将中间点绑定到一个标签,因此当您在屏幕之间拖动主窗口时,它会在主窗口上显示不断变化的 DPI。 The code works in both .NET Framework 4.8 and .NET Core 3.1.该代码适用于 .NET Framework 4.8 和 .NET Core 3.1。

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

Add to an app.manifest:添加到 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>

There is a PropertyChanged Add-in for Fody Fody 有一个 PropertyChanged 插件

https://github.com/Fody/PropertyChanged It takes away a bit of the boilerplate for using INotifyPropertyChanged https://github.com/Fody/PropertyChanged它带走了一些使用 INotifyPropertyChanged 的​​样板文件

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM