1

So in my application I have to types of class objects, Circles and Rectangles. Both which got plotted using a ListBox. The rectangles are green and the circles are the yellow shapes.

You may be asking why am i using the listbox to display them. The benefit here is the freebie of making it possible for users to select the items when the click within the window as seen here.

The problems which i need help on, they all relate to the same topic which is clicking/selection.

Updated: 1/7/2016

  1. Since all listbox items are displayed with a Box Shaped hit test area, it makes problems like this happen. A user wants to select the Rectangle but they get the Yellow Circle instead.

The Problem (Left)| The Desired Goal (Right)

enter image description here

  1. This leads to my last problem which is the hit test for the Yellow Circle shapes. When a user clicks in a negative area it shouldn't actually select the line. It should only select the line when the users cursor is directly over it. as seen in the image on the right. It would be ideal that when it's selected, the highlight indication fits more around the shapes which is highlighted, rather than a huge rectangle.

enter image description here

As far as the code, its rather short, and i combined it for simplicity into lesser files.

MainWindow.xaml

<Window x:Class="WpfApplication1.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:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        Background="Gray">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <Window.Resources>

        <DataTemplate DataType="{x:Type local:RectangleViewModel}" >
            <Border Cursor="Hand" Background="Green" CornerRadius="4" Width="100" Height="100" Margin="10"/>
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:CircleViewModel}" >
            <Path Data="M0,0 C0,0  10,100  100,100" Stroke="Gold" StrokeThickness="5" Cursor="Hand"/>
        </DataTemplate>

    </Window.Resources>

    <!-- Presents the Rectangles -->
    <ListBox x:Name="listBox" ItemsSource="{Binding Items}" SelectionMode="Extended" Background="Transparent">

        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>

        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem" >
                <Setter Property="Canvas.Left" Value="{Binding X}" />
                <Setter Property="Canvas.Top" Value="{Binding Y}" />
            </Style>
        </ListBox.ItemContainerStyle>

        <ListBox.Resources>
            <Style TargetType="{x:Type ListBoxItem}">
                <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            </Style>
        </ListBox.Resources>

    </ListBox>

</Window>

MainWindowViewModel.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication1
{
    public class MainWindowViewModel
    {

        private ObservableCollection<ItemViewModel> items = new ObservableCollection<ItemViewModel>();
        public ObservableCollection<ItemViewModel> Items { get { return items; } }

        public MainWindowViewModel()
        {
            // Populate the view model with some example data.
            items.Add(new RectangleViewModel(250, 200));
            items.Add(new RectangleViewModel(320, 370));
            items.Add(new RectangleViewModel(100, 50));
            items.Add(new RectangleViewModel(350, 25));
            items.Add(new RectangleViewModel(70, 270));

            items.Add(new CircleViewModel(20, 20));
            items.Add(new CircleViewModel(300, 270));
            items.Add(new CircleViewModel(350, 100));
            items.Add(new CircleViewModel(50, 315));
            items.Add(new CircleViewModel(100, 170));
        }
    }

    public class ItemViewModel
    {
        // position coordinates
        public double X { get; set; }
        public double Y { get; set; }
    }

    public class CircleViewModel : ItemViewModel
    {
        // Constructors
        public CircleViewModel(double x, double y)
        {
            this.X = x;
            this.Y = y;
        }
    }
    public class RectangleViewModel : ItemViewModel
    {
        // Constructors
        public RectangleViewModel(double x, double y)
        {
            this.X = x;
            this.Y = y;
        }
    }
}

UPDATED - Closer - ATTEMPT #2

enter image description here

Now I have the ability to drag and move items in the canvas with the hitTest of the click being correct. However for some reason when i try to move shapes in the bottom blue canvas they don't move, where as they do move in the top one. Try it out....

MainWindow.xaml.cs

   using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;

    namespace DragShapes
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }

            bool captured = false;
            double x_shape, x_canvas, y_shape, y_canvas;
            UIElement source = null;

            private void shape_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                Console.WriteLine("MouseDown--Pressed");
                source = (UIElement)sender;
                Mouse.Capture(source);
                captured = true;
                x_shape = Canvas.GetLeft(source);
                x_canvas = e.GetPosition(LayoutRoot).X;
                y_shape = Canvas.GetTop(source);
                y_canvas = e.GetPosition(LayoutRoot).Y;
            }

            private void shape_MouseMove(object sender, MouseEventArgs e)
            {
                if (captured)
                {
                    Console.WriteLine("MouseMove--Pressed");
                    double x = e.GetPosition(LayoutRoot).X;
                    double y = e.GetPosition(LayoutRoot).Y;
                    x_shape += x - x_canvas;
                    Canvas.SetLeft(source, x_shape);
                    x_canvas = x;
                    y_shape += y - y_canvas;
                    Canvas.SetTop(source, y_shape);
                    y_canvas = y;
                }
            }

            private void 

shape_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            Console.WriteLine("MouseUp--Pressed");
            Mouse.Capture(null);
            captured = false;
        }
    }
}

MainWindowViewModel.cs

  using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;


    namespace DragShapes
    {
        public class MainWindowViewModel
        {

            private ObservableCollection<ItemViewModel> items = new ObservableCollection<ItemViewModel>();
            public ObservableCollection<ItemViewModel> Items { get { return items; } }

            public MainWindowViewModel()
            {
                // Populate the view model with some example data.
                items.Add(new RectangleViewModel(250, 200));
                items.Add(new RectangleViewModel(320, 370));
                items.Add(new RectangleViewModel(100, 50));
                items.Add(new RectangleViewModel(350, 25));
                items.Add(new RectangleViewModel(70, 270));

                items.Add(new CircleViewModel(20, 20));
                items.Add(new CircleViewModel(300, 270));
                items.Add(new CircleViewModel(350, 100));
                items.Add(new CircleViewModel(50, 315));
                items.Add(new CircleViewModel(100, 170));
            }
        }

        public class ItemViewModel : INotifyPropertyChanged
        {
            // position coordinates
            private double x = 0;
            public double X
            {
                get { return x; }
                set
                {
                    if (x != value)
                    {
                        x = value;
                        OnPropertyChanged("X");
                    }
                }
            }

            private double y = 0;
            public double Y
            {
                get { return y; }
                set
                {
                    if (y != value)
                    {
                        y = value;
                        OnPropertyChanged("Y");
                    }
                }
            }

            protected void OnPropertyChanged(string name)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(name));
                }
            }
            public event PropertyChangedEventHandler PropertyChanged;
        }

        public class CircleViewModel : ItemViewModel
        {
            // Constructors
            public CircleViewModel(double x, double y)
            {
                this.X = x;
                this.Y = y;
            }
        }
        public class RectangleViewModel : ItemViewModel
        {
            // Constructors
            public RectangleViewModel(double x, double y)
            {
                this.X = x;
                this.Y = y;
            }
        }


    }

MainWindow.xaml

<Window x:Class="DragShapes.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="600" Width="600"
        xmlns:local="clr-namespace:DragShapes">

    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <Window.Resources>

        <DataTemplate DataType="{x:Type local:RectangleViewModel}" >
            <Rectangle Cursor="Hand" Fill="Green" Width="100" Height="100" Margin="10"
                    MouseLeftButtonDown="shape_MouseLeftButtonDown" 
                    MouseMove="shape_MouseMove" 
                    MouseLeftButtonUp="shape_MouseLeftButtonUp"/>
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:CircleViewModel}" >
            <Path Data="M0,0 C0,0  10,100  100,100" Stroke="Gold" StrokeThickness="5" Cursor="Hand"
                  MouseLeftButtonDown="shape_MouseLeftButtonDown" 
                  MouseLeftButtonUp="shape_MouseLeftButtonUp"
                  MouseMove="shape_MouseMove"/>
        </DataTemplate>

    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Canvas x:Name="LayoutRoot" Background="White">

            <Ellipse Fill="Blue" HorizontalAlignment="Center" Height="100" Stroke="Black" VerticalAlignment="Center" Width="100" Canvas.Left="200" Canvas.Top="100"
                     MouseLeftButtonDown="shape_MouseLeftButtonDown" 
                     MouseMove="shape_MouseMove" 
                     MouseLeftButtonUp="shape_MouseLeftButtonUp" />

            <Rectangle Fill="Red" Height="100" Stroke="Black" Width="100" HorizontalAlignment="Left" VerticalAlignment="Bottom" Canvas.Left="10" Canvas.Top="10" 
                       MouseLeftButtonDown="shape_MouseLeftButtonDown" 
                       MouseLeftButtonUp="shape_MouseLeftButtonUp"
                       MouseMove="shape_MouseMove"/>

        </Canvas>

        <Canvas Grid.Row="1"  Background="White" >

            <ItemsControl ItemsSource="{Binding Path=Items}" >

                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas Background="LightBlue" Width="500" Height="500"  />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>

                <!--<ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Ellipse Fill="Green" Width="25" Height="25" 
                                 MouseLeftButtonDown="shape_MouseLeftButtonDown" 
                                 MouseLeftButtonUp="shape_MouseLeftButtonUp"
                                 MouseMove="shape_MouseMove"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>-->

                <ItemsControl.ItemContainerStyle>
                    <Style>
                        <Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
                        <Setter Property="Canvas.Left" Value="{Binding Path=X}" />
                    </Style>
                </ItemsControl.ItemContainerStyle>
            </ItemsControl>

            <!--<ItemsControl ItemsSource="{Binding Path=Items}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemContainerStyle>
                    <Style TargetType="ContentPresenter">
                        <Setter Property="Canvas.Left" Value="{Binding X}"/>
                        <Setter Property="Canvas.Top" Value="{Binding Y}"/>
                    </Style>
                </ItemsControl.ItemContainerStyle>
            </ItemsControl>-->

            <!--<ItemsControl ItemsSource="{Binding Path=Items}">
                --><!--<ItemsControl.ItemContainerStyle>
                    <Style TargetType="ContentPresenter">
                        <Setter Property="Canvas.Left" Value="{Binding X}"/>
                        <Setter Property="Canvas.Top" Value="{Binding Y}"/>
                    </Style>
                </ItemsControl.ItemContainerStyle>--><!--
            </ItemsControl>-->
        </Canvas>
    </Grid>
</Window>
JokerMartini
  • 5,674
  • 9
  • 83
  • 193
  • Just thinking out loud, but perhaps this can be done by setting `IsHitTestVisible` to false for the Grid inside the `ItemTemplate`, and for the `ItemContainerStyle`. And set it to true for the actual paths/border. – Tom Wuyts Jan 07 '16 at 07:11
  • Ill try that and report back. – JokerMartini Jan 07 '16 at 10:42
  • @TomWuyts negatory, that doesn't work but it sounded like a good idea. – JokerMartini Jan 07 '16 at 11:10
  • I see you're using WPF, you could use a tool like Snoop (a xaml inspector) to also monitor what events (and on what elements) are fired when you click that area. That way, you can pinpoint the actual element that is handling the click event and ignore it there, or set that element's `IsHitTestVisible` to false. – Tom Wuyts Jan 07 '16 at 11:24
  • Is there a way to get all elements under a cursor regardless of where they are in the xaml? – JokerMartini Jan 07 '16 at 11:32
  • Yeah, check this: http://stackoverflow.com/questions/45813/wpf-get-elements-under-mouse – Tom Wuyts Jan 07 '16 at 11:43
  • Why are you wrapping your shapes in a Grid? It sounds like your hit test is hitting the Grid wrapping each object, and isn't checking the specific shape itself. I'm not sure you can set `IsHitTestVisible` to false on the Grid without it also being inherited by the shape – Rachel Jan 07 '16 at 16:47
  • I removed all the grids and that didn't help anything. still getting the box shaped hit on click. – JokerMartini Jan 07 '16 at 16:50
  • @JokerMartini It probably has to do with the default template for the ListBox or ListBoxItem then. You could try to customize your own based on [this MSDN example](https://msdn.microsoft.com/en-us/library/ms754242(v=vs.100).aspx). My best guess is you have to remove the Border supplied in the default ListBoxItem template, and replace it with a Path instead. – Rachel Jan 07 '16 at 18:09

1 Answers1

2

Default ListBoxItem template has active background, so we need reset the template like this:

<ListBox.ItemContainerStyle>
    <Style TargetType="ListBoxItem" >
        <Setter Property="Canvas.Left" Value="{Binding X}" />
        <Setter Property="Canvas.Top" Value="{Binding Y}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListBoxItem}">
                    <ContentPresenter/>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="Background" Value="LightBlue"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ListBox.ItemContainerStyle>

Then, we need modify the rectangle and circle templates, to get selection effect, when they are selected:

<DataTemplate DataType="{x:Type local:RectangleViewModel}" >
    <Grid>
        <Border CornerRadius="4"
                Background="{Binding 
                    RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}, 
                    Path=Background}"/>
        <Border Cursor="Hand" Background="Green" CornerRadius="4" Width="100" Height="100" Margin="10"/>
    </Grid>
</DataTemplate>

<DataTemplate DataType="{x:Type local:CircleViewModel}" >
    <Grid>
        <Path Data="M0,0 C0,0  10,100  100,100" StrokeThickness="15"
              Stroke="{Binding 
                RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}, 
                Path=Background}"/>
        <Path Data="M0,0 C0,0  10,100  100,100" Stroke="Gold" StrokeThickness="5" Cursor="Hand"/>
    </Grid>
</DataTemplate>
Igor
  • 86
  • 5
  • check out my second attempt. I was able to get some of the dragging working however when i use data templates it stops working. – JokerMartini Jan 07 '16 at 18:13
  • Awesome! you corrected the selection issue 100%. thank you so much. Lastly is getting the dragging function to work. – JokerMartini Jan 07 '16 at 18:16
  • About drag and drop on ATTEMPT #2. – Igor Jan 07 '16 at 22:02
  • You set 'Canvas.Top' properties on 'ItemsContainerStyle' it not affects to 'ItemViewModel'. So, 'Canvas.GetTop(source)' returns NaN, because event source is not 'ItemsContainerStyle'. You need to use method e.GetPosition(source) to measure sender's position; and set target position as var target = source.DataContext as ItemViewModel; if (target != null){ var diff = e.GetPosition(source) - mouseStartPosition; target.X += diff.X; target.Y += diff.} – Igor Jan 07 '16 at 22:10