C# 可观察收集。清除留下的幻影元素
我有一组转换器,用于在画布上动态定位正方形。正方形存储在可观察的集合中<代码>CanvasPositionScaleConverter将范围内的值转换为0到1之间的值。如果要转换的值超出指定范围,它将引发参数异常 我的问题是,当我清除我的方块集合时,屏幕变空,“幻影”元素似乎被留在后面,转换器继续作用。因此,在调整画布大小时,即使已清除squares集合,仍会引发异常 为什么转换器仍在通过C# 可观察收集。清除留下的幻影元素,c#,wpf,observablecollection,C#,Wpf,Observablecollection,我有一组转换器,用于在画布上动态定位正方形。正方形存储在可观察的集合中CanvasPositionScaleConverter将范围内的值转换为0到1之间的值。如果要转换的值超出指定范围,它将引发参数异常 我的问题是,当我清除我的方块集合时,屏幕变空,“幻影”元素似乎被留在后面,转换器继续作用。因此,在调整画布大小时,即使已清除squares集合,仍会引发异常 为什么转换器仍在通过Squares.Clear()删除的这些元素上运行 注 使用Squares=newobservedcollecti
Squares.Clear()
删除的这些元素上运行
注 使用
Squares=newobservedcollection()
而不是Squares.Clear()
仍然会导致此问题
更新 如果从未添加过
Sqaures
集合,并且按下了Resize
,则不会发生此问题。仅当元素已从集合中删除(显然,如果元素尚未从集合中删除,但超出了有效范围),才会发生这种情况
例外情况详细信息如下所示
Exception thrown: 'System.ArgumentException' in LateExceptionMcve.dll
An unhandled exception of type 'System.ArgumentException' occurred in LateExceptionMcve.dll
Value cannot be greater than max
MCVE MainWindow.xaml
MainWindow.xaml.cs
使用系统;
使用System.Collections.ObjectModel;
使用系统组件模型;
利用制度全球化;
使用System.Runtime.CompilerServices;
使用System.Windows;
使用System.Windows.Data;
命名空间LateExceptionMcve
{
公共部分类主窗口:INotifyPropertyChanged
{
公共主窗口()
{
初始化组件();
DataContext=this;
最小值=0;
最大值=10;
尺寸=最大值-最小值;
正方形=新的可观测集合
{
新广场
{
X=8,
Y=7,
},
};
}
公共事件属性更改事件处理程序属性更改;
私有void OnPropertyChanged([CallerMemberName]字符串propertyName=null)
{
PropertyChanged?.Invoke(这是新的PropertyChangedEventArgs(propertyName));
}
私有void Resize(对象发送器,RoutedEventArgs e)
{
最小值=0;
最大值=5;
尺寸=最大值-最小值;
}
私有无效清除(对象发送方,路由目标)
{
正方形。清除();
}
私人可观测集合广场;
公众可观测集合广场
{
get=>\u平方;
设置
{
_平方=值;
OnPropertyChanged();
}
}
私人双倍大小;
公共双码
{
获取=>\u大小;
设置
{
_大小=值;
OnPropertyChanged();
}
}
私人双(u min),;
公共双分钟
{
get=>\u min;
设置
{
_最小值=数值;
OnPropertyChanged();
}
}
私人双卡最大;
公共双最大值
{
get=>\u max;
设置
{
_最大值=最大值;
OnPropertyChanged();
}
}
}
公共密封类广场:INOTIFYPROPERTYCHANGE
{
公共事件属性更改事件处理程序属性更改;
私有void OnPropertyChanged([CallerMemberName]字符串propertyName=null)
{
PropertyChanged?.Invoke(这是新的PropertyChangedEventArgs(propertyName));
}
私人双x;
公共双X
{
get=>\ux;
设置
{
_x=值;
OnPropertyChanged();
}
}
私人双人房;
公共双Y
{
get=>\u y;
设置
{
_y=值;
OnPropertyChanged();
}
}
}
公共密封类CanvasScaleConverter:IValueConverter
{
私人常数双刻度=100;
公共对象转换(
对象值,
类型targetType,
对象参数,
文化信息(文化)
{
如果(值为双值缩放)
{
返回值
<Window x:Class="LateExceptionMcve.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:LateExceptionMcve"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MainWindow, IsDesignTimeCreatable=True}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Viewbox Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="0"
Stretch="Uniform">
<Border BorderThickness="1"
BorderBrush="Black">
<Border.Resources>
<local:CanvasScaleConverter x:Key="CanvasScaleConverter" />
<local:CanvasPositionScaleConverter x:Key="CanvasPositionScaleConverter" />
</Border.Resources>
<ItemsControl ItemsSource="{Binding Squares}"
Width="{Binding Size, Converter={StaticResource CanvasScaleConverter}}"
Height="{Binding Size, Converter={StaticResource CanvasScaleConverter}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{StaticResource CanvasPositionScaleConverter}">
<Binding Path="X" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Min" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Max" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Size"
Converter="{StaticResource CanvasScaleConverter}" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Top">
<Setter.Value>
<MultiBinding Converter="{StaticResource CanvasPositionScaleConverter}">
<Binding Path="Y" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Min" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Max" />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}"
Path="DataContext.Size"
Converter="{StaticResource CanvasScaleConverter}" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Width" Value="50" />
<Setter Property="Height" Value="50" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Background="Red" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Viewbox>
<Button Grid.Column="0"
Grid.Row="1"
Content="Clear"
Click="Clear" />
<Button Grid.Column="1"
Grid.Row="1"
Content="Resize"
Click="Resize" />
</Grid>
</Window>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;
namespace LateExceptionMcve
{
public partial class MainWindow : INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Min = 0;
Max = 10;
Size = Max - Min;
Squares = new ObservableCollection<Square>
{
new Square
{
X = 8,
Y = 7,
},
};
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void Resize(object sender, RoutedEventArgs e)
{
Min = 0;
Max = 5;
Size = Max - Min;
}
private void Clear(object sender, RoutedEventArgs e)
{
Squares.Clear();
}
private ObservableCollection<Square> _squares;
public ObservableCollection<Square> Squares
{
get => _squares;
set
{
_squares = value;
OnPropertyChanged();
}
}
private double _size;
public double Size
{
get => _size;
set
{
_size = value;
OnPropertyChanged();
}
}
private double _min;
public double Min
{
get => _min;
set
{
_min = value;
OnPropertyChanged();
}
}
private double _max;
public double Max
{
get => _max;
set
{
_max = value;
OnPropertyChanged();
}
}
}
public sealed class Square : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private double _x;
public double X
{
get => _x;
set
{
_x = value;
OnPropertyChanged();
}
}
private double _y;
public double Y
{
get => _y;
set
{
_y = value;
OnPropertyChanged();
}
}
}
public sealed class CanvasScaleConverter : IValueConverter
{
private const double Scale = 100;
public object Convert(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
if (value is double valueToScale)
{
return valueToScale * Scale;
}
return value;
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
public sealed class CanvasPositionScaleConverter : IMultiValueConverter
{
public object Convert(
object[] values,
Type targetType,
object parameter,
CultureInfo culture)
{
if (values.Length == 4 &&
values[0] is double value &&
values[1] is double min &&
values[2] is double max &&
values[3] is double canvasWidthHeight)
{
return canvasWidthHeight * RangeToNormalizedValue(min, max, value);
}
return values;
}
private static double RangeToNormalizedValue(
double min,
double max,
double value)
{
if (min > max)
{
throw new ArgumentException("Min cannot be less than max");
}
if (value < min)
{
throw new ArgumentException("Value cannot be less than min");
}
if (value > max)
{
throw new ArgumentException("Value cannot be greater than max");
}
return (value - min) / (max - min);
}
public object[] ConvertBack(
object value,
Type[] targetTypes,
object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
}
private void Clear(object sender, RoutedEventArgs e)
{
Squares.Clear();
Dispatcher.BeginInvoke(new Action(GC.Collect), DispatcherPriority.ContextIdle);
}
public sealed class CanvasPositionScaleConverter : IMultiValueConverter
{
public object Convert(
object[] values,
Type targetType,
object parameter,
CultureInfo culture)
{
if (values.Length != 4 ||
!(values[0] is double value) ||
!(values[1] is double min) ||
!(values[2] is double max) ||
!(values[3] is double canvasWidthHeight))
{
return null;
}
try
{
return canvasWidthHeight * RangeToNormalizedValue(min, max, value);
}
catch (ArgumentException e) when (e.Message == ValueLessThanMin)
{
return 0;
}
catch (ArgumentException e) when (e.Message == ValueGreaterThanMax)
{
return canvasWidthHeight;
}
}
// This is in a utility class normally
private const string ValueLessThanMin = "Value cannot be less than min";
private const string ValueGreaterThanMax = "Value cannot be greater than max";
private static double RangeToNormalizedValue(
double min,
double max,
double value)
{
if (min > max)
{
throw new ArgumentException("Min cannot be less than max");
}
if (value < min)
{
throw new ArgumentException(ValueLessThanMin);
}
if (value > max)
{
throw new ArgumentException(ValueGreaterThanMax);
}
return (value - min) / (max - min);
}
public object[] ConvertBack(
object value,
Type[] targetTypes,
object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
<Setter Property="Width" Value="50" />
<Setter Property="Height" Value="50" />
<EventSetter Event="Unloaded" Handler="ContentPresenter_Unloaded"/>
</Style>
</ItemsControl.ItemContainerStyle>
public double Max
{
get => _max;
set
{
_max = value;
OnPropertyChanged();
}
}
private void ContentPresenter_Unloaded(object sender, RoutedEventArgs e)
{
}
}