WPF 强制 xaml 重新获取 属性 的值,尽管 属性 的 setter 没有改变
WPF force xaml to re-fetch the property's value although the property's setter didn't change
我正在修改一个现有的 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 与我的问题无关,它只是一个仅在调用 [=28 时才考虑的因素的用例=]).
所以发生的事情是 UI 从 getter 中获取值并且不会刷新该值,除非我使用 setter 将其设置为其他值,然后 'notify'.
如何配置 UI 每次都重新获取值,即使 'thinks' 属性 的值没有改变?或者这是不好的做法而不推荐?
如果您希望框架调用您的 getter,并因此调用您的转换器,您应该实施 INotifyPropertyChanged 并在视图模型中引发 PropertyChanged
事件。
您需要以某种方式确定 DPI 何时更改,然后引发事件。 Convert
方法仅在框架收到任何 data-bound 属性的更改通知时调用(在本例中为 WidgetMiddlePoint
和 ActualWidth
)。
Fody
有一个 PropertyChanged Add-in
https://github.com/Fody/PropertyChanged
它消除了一些使用 INotifyPropertyChanged
的样板文件
有一个 Window DpiChanged 事件可以用于此目的,连同 INotifyPropertyChanged。
下面的代码显示了如何执行此操作。它有一个 RecalculateMiddlePoint 方法,可以创建一个测试点,该点具有 X 和 Y 值的当前 DPI,但显然它应该进行适当的计算。
如果您创建一个 WPF 应用程序,下面的代码会将中间点绑定到一个标签,因此当您在屏幕之间拖动它时,它会在主 window 上显示不断变化的 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>
我正在修改一个现有的 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 与我的问题无关,它只是一个仅在调用 [=28 时才考虑的因素的用例=]).
所以发生的事情是 UI 从 getter 中获取值并且不会刷新该值,除非我使用 setter 将其设置为其他值,然后 'notify'.
如何配置 UI 每次都重新获取值,即使 'thinks' 属性 的值没有改变?或者这是不好的做法而不推荐?
如果您希望框架调用您的 getter,并因此调用您的转换器,您应该实施 INotifyPropertyChanged 并在视图模型中引发 PropertyChanged
事件。
您需要以某种方式确定 DPI 何时更改,然后引发事件。 Convert
方法仅在框架收到任何 data-bound 属性的更改通知时调用(在本例中为 WidgetMiddlePoint
和 ActualWidth
)。
Fody
有一个 PropertyChanged Add-inhttps://github.com/Fody/PropertyChanged 它消除了一些使用 INotifyPropertyChanged
的样板文件有一个 Window DpiChanged 事件可以用于此目的,连同 INotifyPropertyChanged。
下面的代码显示了如何执行此操作。它有一个 RecalculateMiddlePoint 方法,可以创建一个测试点,该点具有 X 和 Y 值的当前 DPI,但显然它应该进行适当的计算。
如果您创建一个 WPF 应用程序,下面的代码会将中间点绑定到一个标签,因此当您在屏幕之间拖动它时,它会在主 window 上显示不断变化的 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>