用户控件中的自定义依赖项 属性 未绑定
Custom dependency property in user control doesn't bind
我有一个图像屏幕用户控件,它包含一个 WPF 图像控件和一些输入控件,它在一些用户控制的数据处理后显示图像数据。我正在使用 MVVM 设计模式,因此它由一个视图和一个视图模型组成——控件是库的一部分,旨在可从多个项目中使用,因此该模型将特定于使用项目。 ImageScreen 看起来像这样:
ImageScreenView XAML:
<UserControl (...)
xmlns:local="clr-namespace:MyCompany.WPFUserControls" x:Class="MyCompany.WPFUserControls.ImageScreenView"
(...) >
<UserControl.DataContext>
<local:ImageScreenViewModel/>
</UserControl.DataContext>
<Border>
(...) // control layout, should not be relevant to the problem
</Border>
</UserControl>
ViewModel 是我的 INotifyPropertyChanged 接口实现的子 class:
NotifyPropertyChanged.cs:
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MyCompany
{
public abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly Dictionary<string, PropertyChangedEventArgs> argumentsCache = new Dictionary<string, PropertyChangedEventArgs>();
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName]string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
if (argumentsCache != null)
{
if (!argumentsCache.ContainsKey(propertyName))
argumentsCache[propertyName] = new PropertyChangedEventArgs(propertyName);
PropertyChanged?.Invoke(this, argumentsCache[propertyName]);
return true;
}
else
return false;
}
else
return false;
}
}
}
视图模型本身主要由一组视图能够绑定的属性组成,如下所示:
ImageScreenViewModel.cs:
using System;
using System.ComponentModel;
using System.Windows.Media.Imaging;
namespace MyCompany.WPFUserControls
{
public class ImageScreenViewModel : NotifyPropertyChanged
{
public ushort[,] ImageData
{
get => imageData;
set => SetProperty(ref imageData, value);
}
(...)
private ushort[,] imageData;
(...)
public ImageScreenViewModel()
{
PropertyChanged += OnPropertyChanged;
}
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
{
switch (eventArgs.PropertyName)
{
(...) // additional property changed event logic
}
}
(...)
}
}
在特定项目中,此控件是 window 主视图的一部分,后者又具有自己的 ViewModel。由于 ImageScreen 应该根据自己视图中的选择来处理和显示数据,因此它应该只公开一个 属性,图像数据(顺便说一句,这是一个 2D ushort 数组),其余的应该被拿走由其视图模型处理。但是,使用控件的主要 window ViewModel 不直接了解 ImageScreen ViewModel,因此我不能简单地访问它并直接传递数据。因此,我在后面的 ImageScreen 代码中将 ImageData 定义为依赖项 属性(我尽可能地避免了后面的代码,但我认为我不能仅在XAML) 与 PropertyChanged 回调,将数据转发到 ViewModel。然后,ImageData 依赖项 属性 旨在通过 MainWindowView 中的 XAML 数据绑定绑定到 MainWindowViewModel。所以后面的ImageScreenView代码是这样的:
ImageScreenView.cs:
using System.Windows;
using System.Windows.Controls;
namespace MyCompany.WPFUserControls
{
public partial class ImageScreenView : UserControl
{
public ushort[,] ImageData
{
get => (ushort[,])GetValue(ImageDataProperty);
set => SetValue(ImageDataProperty, value);
}
public static readonly DependencyProperty ImageDataProperty = DependencyProperty.Register("ImageData", typeof(ushort[,]), typeof(ImageScreenV),
new PropertyMetadata(new PropertyChangedCallback(OnImageDataChanged)));
public ImageScreenView()
{
InitializeComponent();
}
private static void OnImageDataChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
ImageScreenViewModel imageScreenViewModel = (dependencyObject as ImageScreenView).DataContext as ImageScreenViewModel;
if (imageScreenViewModel != null)
imageScreenViewModel.ImageData = (ushort[,])eventArgs.NewValue;
}
}
}
主窗口 XAML 如下所示:
MainWindowViewXAML:
<Window (...)
xmlns:local="clr-namespace:MyCompany.MyProject" x:Class="MyCompany.MyProject.MainWindowView"
xmlns:uc="clr-namespace:MyCompany.WPFUserControls;assembly=WPFUserControls"
(...) >
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid Width="Auto" Height="Auto">
<uc:ImageScreenView (...) ImageData="{Binding LiveFrame}"/>
(...)
<uc:ImageScreenView (...) ImageData="{Binding SavedFrame}"/>
(...)
</Grid>
</Window>
然后在主 window ViewModel 中,绑定属性 LiveFrame 和 SavedFrame 应更新为适当的值。 ViewModel 的相关部分如下所示:
MainWindowViewModel.cs:
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MyCompany.MyProject
{
public class MainWindowViewModel : NotifyPropertyChanged
{
(...)
public ushort[,] LiveFrame
{
get => liveFrame;
set => SetProperty(ref liveFrame, value);
}
public ushort[,] ResultFrame
{
get => resultFrame;
set => SetProperty(ref resultFrame, value);
}
(...)
private ushort[,] liveFrame;
private ushort[,] resultFrame;
(...)
public MainWindowViewModel()
{
PropertyChanged += OnPropertyChanged;
InitCamera();
}
#region methods
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
{
switch (eventArgs.PropertyName)
{
(...) // additional property changed event logic
}
}
private bool InitCamera()
{
(...) // camera device initialization, turn streaming on
}
(...)
private void OnNewFrame(ushort[,] thermalImg, ushort width, ushort height, ...)
{
LiveFrame = thermalImg; // arrival of new frame data
}
private void Stream()
{
while (streaming)
{
(...) // query new frame data
}
}
(...)
}
}
每当新的帧数据到达时,MainWindowViewModel 的 LiveFrame 属性 就会更新。我不质疑相机代码,因为我在其他地方使用它没有问题,并且在调试模式下我可以看到数据正确到达。但是,出于某种原因,当设置 LiveFrame 属性 时,它不会触发 ImageScreen 的 ImageData 依赖项 属性 的更新,即使它绑定到 LiveFrame 属性。我在依赖项 属性 的 setter 和 PropertyChanged 回调中设置了断点,但它们没有被执行。就像绑定不存在一样,尽管我已经在 MainWindowView XAML 中明确设置了它,并且使用 NotifyPropertyChanged 基础 class 我用于 ViewModels 属性 更改应该是(以及事实上)引发刷新数据绑定的 PropertyChanged 事件。我尝试进行双向绑定,并将 UpdateSourceTrigger 设置为 OnPropertyChanged,但都没有效果。最让我困惑的是,MainWindowView 中的其他数据绑定到其 ViewModel 中的属性可以正常工作,其中之一也具有我自己定义的自定义依赖项 属性,尽管在那种情况下它是自定义控件,而不是用户控制。
有谁知道我在哪里搞砸了数据绑定?
编辑: 应 Ed 的要求,我将重新发布完整的 ImageScreenView.xaml 和 ImageScreenViewModel.cs:
ImageScreenV.xaml:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyCompany.WPFUserControls" x:Class="MyCompany.WPFUserControls.ImageScreenV"
xmlns:cc="clr-namespace:MyCompany.WPFCustomControls;assembly=WPFCustomControls"
mc:Ignorable="d" d:DesignWidth="401" d:DesignHeight="300">
<Border BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ImageScreenV}}}"
Background="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ImageScreenV}}}"
BorderThickness="{Binding BorderThickness, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ImageScreenV}}}">
<Border.DataContext>
<local:ImageScreenVM x:Name="viewModel"/>
</Border.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="{Binding ImageWidth}"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition MinHeight="{Binding ImageHeight}"/>
</Grid.RowDefinitions>
<Image MinWidth="{Binding ImageWidth}" MinHeight="{Binding ImageHeight}"
Source="{Binding ScreenImageSource}"/>
<StackPanel Grid.Column="1" Margin="3">
<Label Content="Scaling Method"/>
<ComboBox MinWidth="95" SelectedIndex="{Binding ScalingMethodIndex}">
<ComboBoxItem Content="Manual"/>
<ComboBoxItem Content="MinMax"/>
<ComboBoxItem Content="Sigma 1"/>
<ComboBoxItem Content="Sigma 3"/>
</ComboBox>
<Label Content="Minimum" Margin="0,5,0,0"/>
<cc:DoubleSpinBox DecimalPlaces="2" Prefix="T = " Suffix=" °C" Maximum="65535" Minimum="0" MinHeight="22"
Value="{Binding ScalingMinimum, Mode=TwoWay}" VerticalContentAlignment="Center"
IsEnabled="{Binding ScalingManually}"/>
<Label Content="Maximum"/>
<cc:DoubleSpinBox DecimalPlaces="2" Prefix="T = " Suffix=" °C" Maximum="65535" Minimum="0" MinHeight="22"
Value="{Binding ScalingMaximum, Mode=TwoWay}" VerticalContentAlignment="Center"
IsEnabled="{Binding ScalingManually}"/>
<Label Content="Color Palette" Margin="0,5,0,0"/>
<ComboBox MinWidth="95" SelectedIndex="{Binding ColorPaletteIndex}">
<ComboBoxItem Content="Alarm Blue"/>
<ComboBoxItem Content="Alarm Blue Hi"/>
<ComboBoxItem Content="Alarm Green"/>
<ComboBoxItem Content="Alarm Red"/>
<ComboBoxItem Content="Fire"/>
<ComboBoxItem Content="Gray BW"/>
<ComboBoxItem Content="Gray WB"/>
<ComboBoxItem Content="Ice32"/>
<ComboBoxItem Content="Iron"/>
<ComboBoxItem Content="Iron Hi"/>
<ComboBoxItem Content="Medical 10"/>
<ComboBoxItem Content="Rainbow"/>
<ComboBoxItem Content="Rainbow Hi"/>
<ComboBoxItem Content="Temperature"/>
</ComboBox>
</StackPanel>
</Grid>
</Border>
</UserControl>
ImageScreenVM.cs:
using MyCompany.Vision;
using System;
using System.ComponentModel;
using System.Windows.Media.Imaging;
namespace MyCompany.WPFUserControls
{
public class ImageScreenVM : NotifyPropertyChanged
{
#region properties
public BitmapSource ScreenImageSource
{
get => screenImageSource;
set => SetProperty(ref screenImageSource, value);
}
public int ScalingMethodIndex
{
get => scalingMethodIndex;
set => SetProperty(ref scalingMethodIndex, value);
}
public int ColorPaletteIndex
{
get => colorPaletteIndex;
set => SetProperty(ref colorPaletteIndex, value);
}
public double ScalingMinimum
{
get => scalingMinimum;
set => SetProperty(ref scalingMinimum, value);
}
public double ScalingMaximum
{
get => scalingMaximum;
set => SetProperty(ref scalingMaximum, value);
}
public bool ScalingManually
{
get => scalingManually;
set => SetProperty(ref scalingManually, value);
}
public uint ImageWidth
{
get => imageWidth;
set => SetProperty(ref imageWidth, value);
}
public uint ImageHeight
{
get => imageHeight;
set => SetProperty(ref imageHeight, value);
}
public MyCompany.Vision.Resolution Resolution
{
get => resolution;
set => SetProperty(ref resolution, value);
}
public ushort[,] ImageData
{
get => imageData;
set => SetProperty(ref imageData, value);
}
public Action<ushort[,], byte[,], (double, double), MyCompany.Vision.Processing.Scaling> ScalingAction
{
get => scalingAction;
set => SetProperty(ref scalingAction, value);
}
public Action<byte[,], byte[], MyCompany.Vision.Processing.ColorPalette> ColoringAction
{
get => coloringAction;
set => SetProperty(ref coloringAction, value);
}
#endregion
#region fields
private BitmapSource screenImageSource;
private int scalingMethodIndex;
private int colorPaletteIndex;
private double scalingMinimum;
private double scalingMaximum;
private bool scalingManually;
private uint imageWidth;
private uint imageHeight;
private MyCompany.Vision.Resolution resolution;
private ushort[,] imageData;
private byte[,] scaledImage;
private byte[] coloredImage;
private Action<ushort[,], byte[,], (double, double), MyCompany.Vision.Processing.Scaling> scalingAction;
private Action<byte[,], byte[], MyCompany.Vision.Processing.ColorPalette> coloringAction;
#endregion
public ImageScreenVM()
{
PropertyChanged += OnPropertyChanged;
}
#region methods
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
{
switch (eventArgs.PropertyName)
{
case nameof(ImageData):
if (imageData.GetLength(0) != resolution.Height || imageData.GetLength(1) != resolution.Width)
Resolution = new MyCompany.Vision.Resolution(checked((uint)imageData.GetLength(1)), checked((uint)imageData.GetLength(0)));
goto case nameof(ScalingMaximum);
case nameof(ScalingMethodIndex):
ScalingManually = scalingMethodIndex == (int)MyCompany.Vision.Processing.Scaling.Manual;
goto case nameof(ScalingMaximum);
case nameof(ColorPaletteIndex):
case nameof(ScalingMinimum):
case nameof(ScalingMaximum):
ProcessImage();
break;
case nameof(Resolution):
scaledImage = new byte[resolution.Height, resolution.Width];
coloredImage = new byte[resolution.Pixels() * 3];
ImageWidth = resolution.Width;
ImageHeight = resolution.Height;
break;
}
}
private void ProcessImage()
{
// not sure yet if I stick to this approach
if (scalingAction != null && coloringAction != null)
{
scalingAction(imageData, scaledImage, (scalingMinimum, scalingMaximum), (Processing.Scaling)scalingMethodIndex);
coloringAction(scaledImage, coloredImage, (Processing.ColorPalette)colorPaletteIndex);
}
}
#endregion
}
}
这打破了 MainWindowViewModel
的 LiveFrame
和 SavedFrame
属性的绑定,因为它将 DataContext 设置为不具有这些属性的 ImageScreenViewModel
:
<UserControl.DataContext>
<local:ImageScreenViewModel/>
</UserControl.DataContext>
我不知道 ImageScreenViewModel
的用途,但您可能应该将其替换为 UserControl
class 本身的依赖属性,并绑定到 [=25] =] 使用 RelativeSource
:
的 UserControl
标记
{Binding UcProperty, RelativeSource={RelativeSource AncestorType=UserControl}}
我有一个图像屏幕用户控件,它包含一个 WPF 图像控件和一些输入控件,它在一些用户控制的数据处理后显示图像数据。我正在使用 MVVM 设计模式,因此它由一个视图和一个视图模型组成——控件是库的一部分,旨在可从多个项目中使用,因此该模型将特定于使用项目。 ImageScreen 看起来像这样:
ImageScreenView XAML:
<UserControl (...)
xmlns:local="clr-namespace:MyCompany.WPFUserControls" x:Class="MyCompany.WPFUserControls.ImageScreenView"
(...) >
<UserControl.DataContext>
<local:ImageScreenViewModel/>
</UserControl.DataContext>
<Border>
(...) // control layout, should not be relevant to the problem
</Border>
</UserControl>
ViewModel 是我的 INotifyPropertyChanged 接口实现的子 class:
NotifyPropertyChanged.cs:
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MyCompany
{
public abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly Dictionary<string, PropertyChangedEventArgs> argumentsCache = new Dictionary<string, PropertyChangedEventArgs>();
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName]string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
if (argumentsCache != null)
{
if (!argumentsCache.ContainsKey(propertyName))
argumentsCache[propertyName] = new PropertyChangedEventArgs(propertyName);
PropertyChanged?.Invoke(this, argumentsCache[propertyName]);
return true;
}
else
return false;
}
else
return false;
}
}
}
视图模型本身主要由一组视图能够绑定的属性组成,如下所示:
ImageScreenViewModel.cs:
using System;
using System.ComponentModel;
using System.Windows.Media.Imaging;
namespace MyCompany.WPFUserControls
{
public class ImageScreenViewModel : NotifyPropertyChanged
{
public ushort[,] ImageData
{
get => imageData;
set => SetProperty(ref imageData, value);
}
(...)
private ushort[,] imageData;
(...)
public ImageScreenViewModel()
{
PropertyChanged += OnPropertyChanged;
}
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
{
switch (eventArgs.PropertyName)
{
(...) // additional property changed event logic
}
}
(...)
}
}
在特定项目中,此控件是 window 主视图的一部分,后者又具有自己的 ViewModel。由于 ImageScreen 应该根据自己视图中的选择来处理和显示数据,因此它应该只公开一个 属性,图像数据(顺便说一句,这是一个 2D ushort 数组),其余的应该被拿走由其视图模型处理。但是,使用控件的主要 window ViewModel 不直接了解 ImageScreen ViewModel,因此我不能简单地访问它并直接传递数据。因此,我在后面的 ImageScreen 代码中将 ImageData 定义为依赖项 属性(我尽可能地避免了后面的代码,但我认为我不能仅在XAML) 与 PropertyChanged 回调,将数据转发到 ViewModel。然后,ImageData 依赖项 属性 旨在通过 MainWindowView 中的 XAML 数据绑定绑定到 MainWindowViewModel。所以后面的ImageScreenView代码是这样的:
ImageScreenView.cs:
using System.Windows;
using System.Windows.Controls;
namespace MyCompany.WPFUserControls
{
public partial class ImageScreenView : UserControl
{
public ushort[,] ImageData
{
get => (ushort[,])GetValue(ImageDataProperty);
set => SetValue(ImageDataProperty, value);
}
public static readonly DependencyProperty ImageDataProperty = DependencyProperty.Register("ImageData", typeof(ushort[,]), typeof(ImageScreenV),
new PropertyMetadata(new PropertyChangedCallback(OnImageDataChanged)));
public ImageScreenView()
{
InitializeComponent();
}
private static void OnImageDataChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
ImageScreenViewModel imageScreenViewModel = (dependencyObject as ImageScreenView).DataContext as ImageScreenViewModel;
if (imageScreenViewModel != null)
imageScreenViewModel.ImageData = (ushort[,])eventArgs.NewValue;
}
}
}
主窗口 XAML 如下所示:
MainWindowViewXAML:
<Window (...)
xmlns:local="clr-namespace:MyCompany.MyProject" x:Class="MyCompany.MyProject.MainWindowView"
xmlns:uc="clr-namespace:MyCompany.WPFUserControls;assembly=WPFUserControls"
(...) >
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid Width="Auto" Height="Auto">
<uc:ImageScreenView (...) ImageData="{Binding LiveFrame}"/>
(...)
<uc:ImageScreenView (...) ImageData="{Binding SavedFrame}"/>
(...)
</Grid>
</Window>
然后在主 window ViewModel 中,绑定属性 LiveFrame 和 SavedFrame 应更新为适当的值。 ViewModel 的相关部分如下所示:
MainWindowViewModel.cs:
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MyCompany.MyProject
{
public class MainWindowViewModel : NotifyPropertyChanged
{
(...)
public ushort[,] LiveFrame
{
get => liveFrame;
set => SetProperty(ref liveFrame, value);
}
public ushort[,] ResultFrame
{
get => resultFrame;
set => SetProperty(ref resultFrame, value);
}
(...)
private ushort[,] liveFrame;
private ushort[,] resultFrame;
(...)
public MainWindowViewModel()
{
PropertyChanged += OnPropertyChanged;
InitCamera();
}
#region methods
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
{
switch (eventArgs.PropertyName)
{
(...) // additional property changed event logic
}
}
private bool InitCamera()
{
(...) // camera device initialization, turn streaming on
}
(...)
private void OnNewFrame(ushort[,] thermalImg, ushort width, ushort height, ...)
{
LiveFrame = thermalImg; // arrival of new frame data
}
private void Stream()
{
while (streaming)
{
(...) // query new frame data
}
}
(...)
}
}
每当新的帧数据到达时,MainWindowViewModel 的 LiveFrame 属性 就会更新。我不质疑相机代码,因为我在其他地方使用它没有问题,并且在调试模式下我可以看到数据正确到达。但是,出于某种原因,当设置 LiveFrame 属性 时,它不会触发 ImageScreen 的 ImageData 依赖项 属性 的更新,即使它绑定到 LiveFrame 属性。我在依赖项 属性 的 setter 和 PropertyChanged 回调中设置了断点,但它们没有被执行。就像绑定不存在一样,尽管我已经在 MainWindowView XAML 中明确设置了它,并且使用 NotifyPropertyChanged 基础 class 我用于 ViewModels 属性 更改应该是(以及事实上)引发刷新数据绑定的 PropertyChanged 事件。我尝试进行双向绑定,并将 UpdateSourceTrigger 设置为 OnPropertyChanged,但都没有效果。最让我困惑的是,MainWindowView 中的其他数据绑定到其 ViewModel 中的属性可以正常工作,其中之一也具有我自己定义的自定义依赖项 属性,尽管在那种情况下它是自定义控件,而不是用户控制。
有谁知道我在哪里搞砸了数据绑定?
编辑: 应 Ed 的要求,我将重新发布完整的 ImageScreenView.xaml 和 ImageScreenViewModel.cs:
ImageScreenV.xaml:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyCompany.WPFUserControls" x:Class="MyCompany.WPFUserControls.ImageScreenV"
xmlns:cc="clr-namespace:MyCompany.WPFCustomControls;assembly=WPFCustomControls"
mc:Ignorable="d" d:DesignWidth="401" d:DesignHeight="300">
<Border BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ImageScreenV}}}"
Background="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ImageScreenV}}}"
BorderThickness="{Binding BorderThickness, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ImageScreenV}}}">
<Border.DataContext>
<local:ImageScreenVM x:Name="viewModel"/>
</Border.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="{Binding ImageWidth}"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition MinHeight="{Binding ImageHeight}"/>
</Grid.RowDefinitions>
<Image MinWidth="{Binding ImageWidth}" MinHeight="{Binding ImageHeight}"
Source="{Binding ScreenImageSource}"/>
<StackPanel Grid.Column="1" Margin="3">
<Label Content="Scaling Method"/>
<ComboBox MinWidth="95" SelectedIndex="{Binding ScalingMethodIndex}">
<ComboBoxItem Content="Manual"/>
<ComboBoxItem Content="MinMax"/>
<ComboBoxItem Content="Sigma 1"/>
<ComboBoxItem Content="Sigma 3"/>
</ComboBox>
<Label Content="Minimum" Margin="0,5,0,0"/>
<cc:DoubleSpinBox DecimalPlaces="2" Prefix="T = " Suffix=" °C" Maximum="65535" Minimum="0" MinHeight="22"
Value="{Binding ScalingMinimum, Mode=TwoWay}" VerticalContentAlignment="Center"
IsEnabled="{Binding ScalingManually}"/>
<Label Content="Maximum"/>
<cc:DoubleSpinBox DecimalPlaces="2" Prefix="T = " Suffix=" °C" Maximum="65535" Minimum="0" MinHeight="22"
Value="{Binding ScalingMaximum, Mode=TwoWay}" VerticalContentAlignment="Center"
IsEnabled="{Binding ScalingManually}"/>
<Label Content="Color Palette" Margin="0,5,0,0"/>
<ComboBox MinWidth="95" SelectedIndex="{Binding ColorPaletteIndex}">
<ComboBoxItem Content="Alarm Blue"/>
<ComboBoxItem Content="Alarm Blue Hi"/>
<ComboBoxItem Content="Alarm Green"/>
<ComboBoxItem Content="Alarm Red"/>
<ComboBoxItem Content="Fire"/>
<ComboBoxItem Content="Gray BW"/>
<ComboBoxItem Content="Gray WB"/>
<ComboBoxItem Content="Ice32"/>
<ComboBoxItem Content="Iron"/>
<ComboBoxItem Content="Iron Hi"/>
<ComboBoxItem Content="Medical 10"/>
<ComboBoxItem Content="Rainbow"/>
<ComboBoxItem Content="Rainbow Hi"/>
<ComboBoxItem Content="Temperature"/>
</ComboBox>
</StackPanel>
</Grid>
</Border>
</UserControl>
ImageScreenVM.cs:
using MyCompany.Vision;
using System;
using System.ComponentModel;
using System.Windows.Media.Imaging;
namespace MyCompany.WPFUserControls
{
public class ImageScreenVM : NotifyPropertyChanged
{
#region properties
public BitmapSource ScreenImageSource
{
get => screenImageSource;
set => SetProperty(ref screenImageSource, value);
}
public int ScalingMethodIndex
{
get => scalingMethodIndex;
set => SetProperty(ref scalingMethodIndex, value);
}
public int ColorPaletteIndex
{
get => colorPaletteIndex;
set => SetProperty(ref colorPaletteIndex, value);
}
public double ScalingMinimum
{
get => scalingMinimum;
set => SetProperty(ref scalingMinimum, value);
}
public double ScalingMaximum
{
get => scalingMaximum;
set => SetProperty(ref scalingMaximum, value);
}
public bool ScalingManually
{
get => scalingManually;
set => SetProperty(ref scalingManually, value);
}
public uint ImageWidth
{
get => imageWidth;
set => SetProperty(ref imageWidth, value);
}
public uint ImageHeight
{
get => imageHeight;
set => SetProperty(ref imageHeight, value);
}
public MyCompany.Vision.Resolution Resolution
{
get => resolution;
set => SetProperty(ref resolution, value);
}
public ushort[,] ImageData
{
get => imageData;
set => SetProperty(ref imageData, value);
}
public Action<ushort[,], byte[,], (double, double), MyCompany.Vision.Processing.Scaling> ScalingAction
{
get => scalingAction;
set => SetProperty(ref scalingAction, value);
}
public Action<byte[,], byte[], MyCompany.Vision.Processing.ColorPalette> ColoringAction
{
get => coloringAction;
set => SetProperty(ref coloringAction, value);
}
#endregion
#region fields
private BitmapSource screenImageSource;
private int scalingMethodIndex;
private int colorPaletteIndex;
private double scalingMinimum;
private double scalingMaximum;
private bool scalingManually;
private uint imageWidth;
private uint imageHeight;
private MyCompany.Vision.Resolution resolution;
private ushort[,] imageData;
private byte[,] scaledImage;
private byte[] coloredImage;
private Action<ushort[,], byte[,], (double, double), MyCompany.Vision.Processing.Scaling> scalingAction;
private Action<byte[,], byte[], MyCompany.Vision.Processing.ColorPalette> coloringAction;
#endregion
public ImageScreenVM()
{
PropertyChanged += OnPropertyChanged;
}
#region methods
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
{
switch (eventArgs.PropertyName)
{
case nameof(ImageData):
if (imageData.GetLength(0) != resolution.Height || imageData.GetLength(1) != resolution.Width)
Resolution = new MyCompany.Vision.Resolution(checked((uint)imageData.GetLength(1)), checked((uint)imageData.GetLength(0)));
goto case nameof(ScalingMaximum);
case nameof(ScalingMethodIndex):
ScalingManually = scalingMethodIndex == (int)MyCompany.Vision.Processing.Scaling.Manual;
goto case nameof(ScalingMaximum);
case nameof(ColorPaletteIndex):
case nameof(ScalingMinimum):
case nameof(ScalingMaximum):
ProcessImage();
break;
case nameof(Resolution):
scaledImage = new byte[resolution.Height, resolution.Width];
coloredImage = new byte[resolution.Pixels() * 3];
ImageWidth = resolution.Width;
ImageHeight = resolution.Height;
break;
}
}
private void ProcessImage()
{
// not sure yet if I stick to this approach
if (scalingAction != null && coloringAction != null)
{
scalingAction(imageData, scaledImage, (scalingMinimum, scalingMaximum), (Processing.Scaling)scalingMethodIndex);
coloringAction(scaledImage, coloredImage, (Processing.ColorPalette)colorPaletteIndex);
}
}
#endregion
}
}
这打破了 MainWindowViewModel
的 LiveFrame
和 SavedFrame
属性的绑定,因为它将 DataContext 设置为不具有这些属性的 ImageScreenViewModel
:
<UserControl.DataContext>
<local:ImageScreenViewModel/>
</UserControl.DataContext>
我不知道 ImageScreenViewModel
的用途,但您可能应该将其替换为 UserControl
class 本身的依赖属性,并绑定到 [=25] =] 使用 RelativeSource
:
UserControl
标记
{Binding UcProperty, RelativeSource={RelativeSource AncestorType=UserControl}}