Home > Blockchain >  How can I recognize a second click on a line
How can I recognize a second click on a line

Time:01-26

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>
  • Related