[英]How can I recognize a second click on a line
我有一个带有一堆线条的 canvas,我设置了它,这样如果我点击一条线,颜色就会改变。 然后我希望能够再次单击此行并将颜色重置为黑色,但我遇到了一些麻烦。
我试过的:
private void Line_MouseDown(object sender, MouseButtonEventArgs e)
{
Color selectionColor = (Color)ColorConverter.ConvertFromString("#FF490AF6");
SolidColorBrush selectionBrush = new SolidColorBrush(selectionColor);
SolidColorBrush blackBrush = new SolidColorBrush();
blackBrush.Color = Colors.Black;
Line line = (Line)sender;
if (line.Stroke == selectionBrush)
{
line.Stroke = blackBrush;
}
else
{
line.Stroke = selectionBrush;
}
}
当你比较画笔时
line.Stroke == selectionBrush
这将始终是与您期望的不同的画笔,因为您刚刚实例化了一个新画笔。 用线
SolidColorBrush selectionBrush = new SolidColorBrush(selectionColor);
你应该把那个刷子声明在拥有的class中,声明的时候设置它。
private SolidColorBrush selectionBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF490AF6"));
然后将其用于设置和比较。
private void Line_MouseDown(object sender, MouseButtonEventArgs e)
{
Line line = (Line)sender;
if (line.Stroke == selectionBrush)
{
line.Stroke = Brushes.Black;
}
else
{
line.Stroke = selectionBrush;
}
}
你也可以只使用 Brushes.Black。
使用列表框进行选择处理的替代方法
<ListBox ItemsSource="{Binding Lines}" b:SelectionBehavior.SelectedItems="{Binding SelectedLines}" Background="Beige" SelectionMode="Multiple">
<ListBox.Resources>
<local:SubstractingConverter x:Key="SubstractingConverter" LowerBound="0"/>
<local:MinConverter x:Key="MinConverter"/>
</ListBox.Resources>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{StaticResource MinConverter}">
<Binding Path="X1"/>
<Binding Path="X2"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Top">
<Setter.Value>
<MultiBinding Converter="{StaticResource MinConverter}">
<Binding Path="Y1"/>
<Binding Path="Y2"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid>
<Line x:Name="LineElement" StrokeThickness="2" Stroke="Black">
<Line.X1>
<MultiBinding Converter="{StaticResource SubstractingConverter}">
<Binding Path="X1"/>
<Binding Path="X2"/>
</MultiBinding>
</Line.X1>
<Line.Y1>
<MultiBinding Converter="{StaticResource SubstractingConverter}">
<Binding Path="Y1"/>
<Binding Path="Y2"/>
</MultiBinding>
</Line.Y1>
<Line.X2>
<MultiBinding Converter="{StaticResource SubstractingConverter}">
<Binding Path="X2"/>
<Binding Path="X1"/>
</MultiBinding>
</Line.X2>
<Line.Y2>
<MultiBinding Converter="{StaticResource SubstractingConverter}">
<Binding Path="Y2"/>
<Binding Path="Y1"/>
</MultiBinding>
</Line.Y2>
</Line>
<Line x:Name="HighlightElement" StrokeThickness="4" Stroke="Transparent"
X1="{Binding X1, ElementName=LineElement}" Y1="{Binding Y1, ElementName=LineElement}"
X2="{Binding X2, ElementName=LineElement}" Y2="{Binding Y2, ElementName=LineElement}">
</Line>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="LineElement" Property="Stroke" Value="HotPink"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="HighlightElement" Property="Stroke" Value="LightBlue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
和视图模型代码
public ObservableCollection<LineViewModel> Lines { get; } = new ObservableCollection<LineViewModel>();
public ObservableCollection<LineViewModel> SelectedLines { get; } = new ObservableCollection<LineViewModel>();
专线 class
public class LineViewModel : ObservableObject
{
private double _x1;
private double _y1;
private double _x2;
private double _y2;
public double X1 { get => _x1; set => SetValue(ref _x1, value); }
public double Y1 { get => _y1; set => SetValue(ref _y1, value); }
public double X2 { get => _x2; set => SetValue(ref _x2, value); }
public double Y2 { get => _y2; set => SetValue(ref _y2, value); }
}
转换器
public class SubstractingConverter : IMultiValueConverter
{
public double LowerBound { get; set; } = double.MinValue;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Contains(DependencyProperty.UnsetValue))
return Binding.DoNothing;
return Math.Max(LowerBound, (double)values[0] - (double)values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
public class MinConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Contains(DependencyProperty.UnsetValue))
return Binding.DoNothing;
return Math.Min((double)values[0], (double)values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
行为
public class SelectionBehavior
{
private static readonly DependencyProperty BehaviorProperty = DependencyProperty.Register("BehaviorItems", typeof(SelectionBehavior), typeof(SelectionBehavior));
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached("SelectedItems", typeof(INotifyCollectionChanged), typeof(SelectionBehavior), new PropertyMetadata(SelectedItems_Changed));
public static INotifyCollectionChanged GetSelectedItems(DependencyObject obj)
{
return (INotifyCollectionChanged)obj.GetValue(SelectedItemsProperty);
}
public static void SetSelectedItems(DependencyObject obj, INotifyCollectionChanged value)
{
obj.SetValue(SelectedItemsProperty, value);
}
private static void SelectedItems_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
INotifyCollectionChanged selectedItems;
if (d is ListBox listBox)
selectedItems = (INotifyCollectionChanged)listBox.SelectedItems;
else if (d is MultiSelector ms)
selectedItems = (INotifyCollectionChanged)ms.SelectedItems;
else
selectedItems = (INotifyCollectionChanged)d.GetType().GetProperty("SelectedItems").GetValue(d);
var behavior = (SelectionBehavior)d.GetValue(BehaviorProperty);
if (behavior != null)
behavior.Detach(d);
if (e.NewValue != null)
{
behavior = new SelectionBehavior(selectedItems, (INotifyCollectionChanged)e.NewValue);
behavior._sourceItems.CollectionChanged += behavior.OnCollectionChanged;
behavior._targetItems.CollectionChanged += behavior.OnCollectionChanged;
d.SetValue(BehaviorProperty, behavior);
if (d is FrameworkElement fe)
fe.Unloaded += behavior.Unloaded;
}
}
private readonly INotifyCollectionChanged _sourceItems;
private readonly INotifyCollectionChanged _targetItems;
private bool _isSyncing;
public SelectionBehavior(INotifyCollectionChanged sourceItems, INotifyCollectionChanged targetItems)
{
_sourceItems = sourceItems;
_targetItems = targetItems;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_isSyncing)
return;
try
{
_isSyncing = true;
if (e.Action == NotifyCollectionChangedAction.Move)
throw new NotImplementedException(); // not sure if it has old and new items like with replace
var syncTo = sender == _sourceItems ? (IList)_targetItems : (IList)_sourceItems;
if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace)
{
for (int i = 0; i < e.OldItems.Count; i++)
syncTo.RemoveAt(e.OldStartingIndex + i);
}
if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Replace)
{
for (int i = 0; i < e.NewItems.Count; i++)
syncTo.Insert(e.NewStartingIndex + i, e.NewItems[i]);
}
if (e.Action == NotifyCollectionChangedAction.Reset)
syncTo.Clear();
}
finally
{
_isSyncing = false;
}
}
private void Unloaded(object sender, RoutedEventArgs e)
{
((FrameworkElement)sender).Unloaded -= Unloaded;
Detach((DependencyObject)sender);
}
public void Detach(DependencyObject d)
{
_sourceItems.CollectionChanged -= OnCollectionChanged;
_targetItems.CollectionChanged -= OnCollectionChanged;
d.ClearValue(BehaviorProperty);
}
}
一个非常简单的解决方案涉及一个自定义附加属性IsChecked
来保存元素的 state 和一个Style
来定义一个Trigger
来切换Shape.Stroke
值。
以下示例使用以所有Line
元素为目标的隐式Style
。
如果您需要每条Line
都有不同的颜色,则必须创建单独的 styles。
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static bool GetIsChecked(DependencyObject attachingElement)
=> (bool)attachingElement.GetValue(IsCheckedProperty);
public static void SetIsChecked(DependencyObject attachingElement, bool value)
=> attachingElement.SetValue(IsCheckedProperty, value);
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.RegisterAttached(
"IsChecked",
typeof(bool),
typeof(MainWindow),
new PropertyMetadata(default));
private void OnShapePreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var shape = sender as DependencyObject;
// Toggle the attached IsChecked value
SetIsChecked(shape, GetIsChecked(shape) ^ true);
}
}
主窗口.xaml
以下Style
将应用于作为MainWindow
可视化树子级的每一Line
。
<Window>
<Window.Resources>
<Style TargetType="Line">
<Setter Property="Stroke"
Value="Black" />
<Setter Property="StrokeThickness"
Value="4" />
<EventSetter Event="PreviewMouseLeftButtonUp"
Handler="OnShapePreviewMouseLeftButtonUp" />
<Style.Triggers>
<Trigger Property="local:MainWindow.IsChecked"
Value="True">
<Setter Property="Stroke"
Value="#FF490AF6" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Canvas>
<Line Canvas.Left="10"
Canvas.Top="10"
X1="0"
X2="50"
Y1="10"
Y2="10" />
<Line Canvas.Left="20"
Canvas.Top="20"
X1="0"
X2="50"
Y1="10"
Y2="10" />
</Canvas>
</Window>
或者将上面的代码移动到一个简单的附加行为中:
ShapeToggleBehavior.cs
public class ShapeToggleBehavior : DependencyObject
{
public static bool GetIsChecked(DependencyObject attachingElement)
=> (bool)attachingElement.GetValue(IsCheckedProperty);
public static void SetIsChecked(DependencyObject attachingElement, bool value)
=> attachingElement.SetValue(IsCheckedProperty, value);
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.RegisterAttached(
"IsChecked",
typeof(bool),
typeof(ShapeToggleBehavior),
new PropertyMetadata(default(bool), OnIsCheckedChanged));
public static Brush GetCheckedStroke(DependencyObject attachingElement)
=> (Brush)attachingElement.GetValue(CheckedStrokeProperty);
public static void SetCheckedStroke(DependencyObject attachingElement, Brush value)
=> attachingElement.SetValue(CheckedStrokeProperty, value);
public static readonly DependencyProperty CheckedStrokeProperty = DependencyProperty.RegisterAttached(
"CheckedStroke",
typeof(Brush),
typeof(ShapeToggleBehavior),
new PropertyMetadata(Brushes.LightBlue));
public static Brush GetUncheckedStroke(DependencyObject attachingElement)
=> (Brush)attachingElement.GetValue(UncheckedStrokeProperty);
public static void SetUncheckedStroke(DependencyObject attachingElement, Brush value)
=> attachingElement.SetValue(UncheckedStrokeProperty, value);
public static readonly DependencyProperty UncheckedStrokeProperty = DependencyProperty.RegisterAttached(
"UncheckedStroke",
typeof(Brush),
typeof(ShapeToggleBehavior),
new PropertyMetadata(Brushes.Black));
public static bool GetIsEnabled(DependencyObject attachingElement)
=> (bool)attachingElement.GetValue(IsEnabledProperty);
public static void SetIsEnabled(DependencyObject attachingElement, bool value)
=> attachingElement.SetValue(IsEnabledProperty, value);
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached(
"IsEnabled",
typeof(bool),
typeof(ShapeToggleBehavior),
new PropertyMetadata(default(bool), OnIsEnabledChanged));
private static void OnIsEnabledChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (attachingElement is not Shape shape)
{
return;
}
if ((bool)e.NewValue)
{
WeakEventManager<Shape, MouseButtonEventArgs>.AddHandler(shape, nameof(shape.PreviewMouseLeftButtonUp), OnShapePreviewMouseLeftButtonUp);
shape.Stroke ??= GetUncheckedStroke(shape);
}
else
{
WeakEventManager<Shape, MouseButtonEventArgs>.RemoveHandler(shape, nameof(shape.PreviewMouseLeftButtonUp), OnShapePreviewMouseLeftButtonUp);
}
}
private static void OnIsCheckedChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
var shape = attachingElement as Shape;
bool isChecked = GetIsChecked(shape);
shape.Stroke = isChecked
? GetCheckedStroke(shape)
: GetUncheckedStroke(shape);
}
private static void OnShapePreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var shape = sender as Shape;
// Toggle IsChecked
bool isChecked = GetIsChecked(shape) ^ true;
SetIsChecked(shape, isChecked);
}
}
主窗口.xaml
<Window>
<Canvas>
<Line local:ShapeToggleBehavior.IsEnabled="True"
StrokeThickness="4"
Canvas.Left="10"
Canvas.Top="10"
X1="0"
X2="50"
Y1="10"
Y2="10" />
<Line local:ShapeToggleBehavior.IsEnabled="True"
local:ShapeToggleBehavior.CheckedStroke="Red"
local:ShapeToggleBehavior.UncheckedStroke="Green"
StrokeThickness="4"
Canvas.Left="20"
Canvas.Top="20"
X1="0"
X2="50"
Y1="10"
Y2="10" />
</Canvas>
</Window>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.