I have a canvas with a bunch of lines and I have it set so that if I click a line the color changes. I want to then be able to click this line again and reset the color to black but I'm having a bit of trouble.
What I have tried:
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;
}
}
CodePudding user response:
When you compare brushes with
line.Stroke == selectionBrush
This will always be a different brush to what you expect because you just instantiated a new brush. with the line
SolidColorBrush selectionBrush = new SolidColorBrush(selectionColor);
You should make that a brush declared in the owning class, set it up when you declare it.
private SolidColorBrush selectionBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF490AF6"));
Then use that for setting and comparison.
private void Line_MouseDown(object sender, MouseButtonEventArgs e)
{
Line line = (Line)sender;
if (line.Stroke == selectionBrush)
{
line.Stroke = Brushes.Black;
}
else
{
line.Stroke = selectionBrush;
}
}
And you can just use Brushes.Black as well.
CodePudding user response:
an alternative using Listbox for the selection handling
<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>
and viewmodel code
public ObservableCollection<LineViewModel> Lines { get; } = new ObservableCollection<LineViewModel>();
public ObservableCollection<LineViewModel> SelectedLines { get; } = new ObservableCollection<LineViewModel>();
Line 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); }
}
converters
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();
}
}
behavior
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);
}
}
CodePudding user response:
A very simple solution involves a custom attached property IsChecked
to save the element's state and a Style
to define a Trigger
to toggle the Shape.Stroke
value.
The following example uses an implicit Style
that targets all Line
elements.
If you require every Line
to have a different color you would have to create individual 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);
}
}
MainWindow.xaml
The following Style
will be applied to every Line
that is a child of the visual tree of MainWindow
.
<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>
</Canvas>
</Window>