JokerMartini
JokerMartini

Reputation: 6147

Clicking listbox usercontrol items in wpf

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>

Upvotes: 1

Views: 724

Answers (1)

Igor
Igor

Reputation: 96

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>

Upvotes: 2

Related Questions