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
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)
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.
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
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>
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>
Related
I have a canvas, and I want to give it grid lines as a background, but I want there to be a constant number of grid lines that divide the canvas into equally-sized sections, rather than just have equally-spaced grid-lines. I want this to be preserved when the canvas is resized by the user.
How should I do this?
Here is a solution which is based in two wpf ListView controls behind the canvas(one for rows and second for columns). The content of the columns related ListView control is a rectangle.
Updated version - Managed Grid Lines Control. Here you can manage the number of grid lines and their visibility.
Xaml code - grid lines control:
<UserControl x:Class="CAnvasWithGrid.GridLineControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:canvasWithGrid="clr-namespace:CAnvasWithGrid"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" x:Name="This">
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<Style TargetType="ListView">
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style x:Key="ListViewItemStyle" TargetType="ListViewItem">
<Setter Property="Background" Value="Transparent"/>
</Style>
<DataTemplate x:Key="InnerListviewDataTemplate" DataType="{x:Type canvasWithGrid:CellModel}">
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Margin="0" StrokeDashArray="4" Stroke="Black" StrokeThickness="0.5" Fill="Transparent"/>
</DataTemplate>
<DataTemplate x:Key="ListviewDataTemplate" DataType="{x:Type canvasWithGrid:RowModel}">
<ListView ItemsSource="{Binding CellModels}" BorderBrush="#00FFFFFF" BorderThickness="0" Margin="0"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding CellModels, Converter={canvasWithGrid:CollectionLength2NumberConverter}}"></UniformGrid>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemStyle}">
<Setter Property="Margin" Value="0"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter Content="{TemplateBinding Content}" Margin="0"
ContentTemplate="{StaticResource InnerListviewDataTemplate}" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate" Value="{StaticResource InnerListviewDataTemplate}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</DataTemplate>
</Grid.Resources>
<ListView ItemsSource="{Binding ElementName=This, Path=RowModels}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding ElementName=This, Path=RowModels, Converter={canvasWithGrid:CollectionLength2NumberConverter}}"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemStyle}">
<Setter Property="Margin" Value="0"></Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter Content="{TemplateBinding Content}" Margin="-1"
ContentTemplate="{StaticResource ListviewDataTemplate}" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate" Value="{StaticResource ListviewDataTemplate}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
Grid lines control - code behind
/// <summary>
/// Interaction logic for GridLineControl.xaml
/// </summary>
public partial class GridLineControl : UserControl
{
public GridLineControl()
{
InitializeComponent();
}
public static readonly DependencyProperty NumberOfColumnsProperty = DependencyProperty.Register(
"NumberOfColumns", typeof (int), typeof (GridLineControl), new PropertyMetadata(default(int), NumberOfColumnsChangedCallback));
private static void NumberOfColumnsChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var numberOfRows = (int)dependencyObject.GetValue(NumberOfRowsProperty);
var numberOfColumns = (int)args.NewValue;
if (numberOfColumns == 0 || numberOfRows == 0) return;
var rowModelsCollection = GetRowModelsCollection(numberOfRows, numberOfColumns);
dependencyObject.SetValue(RowModelsProperty, rowModelsCollection);
}
public int NumberOfColumns
{
get { return (int) GetValue(NumberOfColumnsProperty); }
set { SetValue(NumberOfColumnsProperty, value); }
}
public static readonly DependencyProperty NumberOfRowsProperty = DependencyProperty.Register(
"NumberOfRows", typeof (int), typeof (GridLineControl), new PropertyMetadata(default(int), NumberOfRowsChangedCallback));
private static void NumberOfRowsChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var numberOfRows = (int)args.NewValue;
var numberOfColumns = (int)dependencyObject.GetValue(NumberOfColumnsProperty);
if(numberOfColumns == 0 || numberOfRows == 0) return;
var rowModelsCollection = GetRowModelsCollection(numberOfRows, numberOfColumns);
dependencyObject.SetValue(RowModelsProperty, rowModelsCollection);
}
private static ObservableCollection<RowModel> GetRowModelsCollection(int numberOfRows, int numberOfColumns)
{
var rowModelsCollection = new ObservableCollection<RowModel>();
for (var i = 0; i < numberOfRows; i++)
{
rowModelsCollection.Add(new RowModel(numberOfColumns) {Position = (i + 1).ToString()});
}
return rowModelsCollection;
}
public int NumberOfRows
{
get { return (int) GetValue(NumberOfRowsProperty); }
set { SetValue(NumberOfRowsProperty, value); }
}
public static readonly DependencyProperty RowModelsProperty = DependencyProperty.Register("RowModels",
typeof(ObservableCollection<RowModel>), typeof(GridLineControl),
new PropertyMetadata(default(ObservableCollection<RowModel>)));
public ObservableCollection<RowModel> RowModels
{
get { return (ObservableCollection<RowModel>)GetValue(RowModelsProperty); }
private set { SetValue(RowModelsProperty, value); }
}
}
Models:
public class RowModel:BaseGridMember
{
public RowModel(int numberOfCellsInRow)
{
CellModels = new ObservableCollection<CellModel>();
for (int i = 0; i < numberOfCellsInRow; i++)
{
CellModels.Add(new CellModel{Position = (i+1).ToString()});
}
}
public ObservableCollection<CellModel> CellModels { get; set; }
}
public class CellModel:BaseGridMember
{
}
public class BaseGridMember:BaseObservableObject
{
private string _position;
public string Position
{
get { return _position; }
set
{
_position = value;
OnPropertyChanged();
}
}
}
Main window xaml code - as you can see here is a ImageContol instead of Canvas but you can replace it:
<Window x:Class="CAnvasWithGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:canvasWithGrid="clr-namespace:CAnvasWithGrid"
Title="MainWindow" Height="525" Width="525" x:Name="This">
<Grid Tag="{Binding ElementName=This}">
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="Bool2VisConvKey" />
</Grid.Resources>
<Grid.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show Grid Lines" Command="{Binding ShowGridLinesCommand}"/>
</ContextMenu>
</Grid.ContextMenu>
<Image Source="Resources/Koala.jpg" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MouseDown="UIElement_OnMouseDown"/>
<canvasWithGrid:GridLineControl NumberOfRows="50" NumberOfColumns="50"
IsHitTestVisible="False" Visibility="{Binding ElementName=This, Path=AreGridLineVisible, Converter={StaticResource Bool2VisConvKey}, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Main window code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ShowGridLinesCommand = new RelayCommand(ShowGridLineManageCommand);
AreGridLineVisible = true;
}
private void ShowGridLineManageCommand()
{
AreGridLineVisible = !AreGridLineVisible;
}
public static readonly DependencyProperty AreGridLineVisibleProperty = DependencyProperty.Register(
"AreGridLineVisible", typeof (bool), typeof (MainWindow), new PropertyMetadata(default(bool)));
public bool AreGridLineVisible
{
get { return (bool) GetValue(AreGridLineVisibleProperty); }
set { SetValue(AreGridLineVisibleProperty, value); }
}
public static readonly DependencyProperty ShowGridLinesCommandProperty = DependencyProperty.Register(
"ShowGridLinesCommand", typeof (ICommand), typeof (MainWindow), new PropertyMetadata(default(ICommand)));
public ICommand ShowGridLinesCommand
{
get { return (ICommand) GetValue(ShowGridLinesCommandProperty); }
set { SetValue(ShowGridLinesCommandProperty, value); }
}
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
}
}
How it looks like:
Sounds like a candidate for a custom control with custom drawing. You don't really want to use multiple FrameworkElements like "Line" if you are expecting many grid-lines for performance reasons.
So you'd create a customControl GridLinesControl and overwrite the OnRender method. You can get the actual width and height of the component using the properties ActualWidth and ActualHeight, divide by the number of grid lines you want and draw lines using drawingContext.DrawLine.
The easiest way would be to add the GridLinesControl you've made underneath the canvas, taking up the same space (so it has the right ActualWidth and ActualHeight) like this:
<Grid>
<myControls:GridLinesControl/>
<Canvas ... />
</Grid>
So it's always underneath.
I am implementing a chat application like this image:
I started by creating a ListBox and setting a ListBox.ItemTemplate, but i can't figure out how can i control the ListBox to add an item with the layout as a message received or as send (just like Whatsapp).
Here is my code:
<ListBox Name="ChatListBox" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Background="#00FFFFFF" BorderBrush="{x:Null}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Background="#00FFFFFF"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<!--If the user sends a msg-->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="9*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Margin="0" BorderThickness="1" BorderBrush="#9f9f9f" Background="#c4df9b" CornerRadius="10">
<TextBlock Name="MsgText" Background="#c4df9b" Foreground="Black" TextAlignment="Center" TextWrapping="Wrap" Margin="5" Text="{Binding text}" FontSize="14"/>
</Border>
<Image Grid.Column="1" Source="Images/user.png" Margin="5" Height="{Binding ElementName=MsgText, Path=ActualHeight}"/>
<!--
If the user receives a msg
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="Images/user.png" Margin="5" Height="{Binding ElementName=MsgText, Path=ActualHeight}"/>
<Border Grid.Column="1" Margin="0" BorderThickness="1" BorderBrush="#9f9f9f" Background="#c4df9b" CornerRadius="10">
<TextBlock Name="MsgText" Background="#c4df9b" Foreground="Black" TextAlignment="Center" TextWrapping="Wrap" Margin="5" Text="{Binding text}" FontSize="14"/>
</Border>-->
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is my c# code:
List<ChatItem> chatItem = new List<ChatItem>();
chatItem.Add(new ChatItem() { text = "Hello...", isFromUser = false });
chatItem.Add(new ChatItem() { text = "hi!", isFromUser = true });
chatItem.Add(new ChatItem() { text = "this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test", isFromUser = false });
ChatListBox.ItemsSource = chatItem;
This is how the ListBox is redered:
Is there any way of adding an IF statement at the ListBox with WPF? or how can i control which ListBox.ItemTemplate to add.
If your messages are of the same class you could use itemsTemplateSelector as in http://codingbandit.com/blog/?p=8
if there are different classes you should just use the datatemplate datatype property as in Conditional List itemtemplate or datatemplate in WPF
Another option is to simply use a data trigger:
<DataTemplate x:Key="ToTemplate">
... etc ...
</DataTemplate>
<DataTemplate x:Key="FromTemplate">
... etc ...
</DataTemplate>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding isFromUser}" Value="false">
<Setter Property="Template" Value="{StaticResource ToTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding isFromUser}" Value="true">
<Setter Property="Template" Value="{StaticResource FromTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
Maybe this will give you an idea
Xaml
<Window x:Class="Q1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Q1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="intDataTemplate">
<TextBox Text="{Binding Path=.}" Width="80"/>
</DataTemplate>
<DataTemplate x:Key="stringDataTemplate">
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
<local:MyDataTemplateSelector IntDataTemplate="{StaticResource intDataTemplate}"
StringDataTemplate="{StaticResource stringDataTemplate}"
x:Key="myDataTemplateSelector"/>
</Window.Resources>
<Grid>
<ListBox x:Name="myListBox" ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}">
</ListBox>
</Grid>
Code-Behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 Q1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<System.Object> myList = new List<object>();
myList.Add(1);
myList.Add("Alpha");
myList.Add(2);
myList.Add("Beta");
myList.Add(3);
myList.Add("Gamma");
myListBox.DataContext = myList;
}
}
}
DataTemplateSelector
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Q1
{
public class MyDataTemplateSelector : System.Windows.Controls.DataTemplateSelector
{
public System.Windows.DataTemplate IntDataTemplate { get; set; }
public System.Windows.DataTemplate StringDataTemplate { get; set; }
public MyDataTemplateSelector()
{
IntDataTemplate = new System.Windows.DataTemplate();
StringDataTemplate = new System.Windows.DataTemplate();
}
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
if (item is Int32)
{
return IntDataTemplate;
}
else
{
return StringDataTemplate;
}
}
}
}
You could use DataTemplateSelector to select the Template according with your data, here's an example.
For this example, I'll use messages, a client message and a server message:
public abstract class Message
{
public string Content { get; set; }
public override string ToString()
{
return Content;
}
}
public class ServerMessage : Message
{
}
public class ClientMessage : Message
{
}
In this way, I can check the type of the object and apply certain template.
Let's define our templates and selector:
XAML:
<DataTemplate x:Key="clientTemplate" >
<TextBlock Text="{Binding Content}"
Foreground="Red"/>
</DataTemplate>
<DataTemplate x:Key="serverClient">
<TextBlock Text="{Binding Content}"
Foreground="Green"/>
</DataTemplate>
<local:MessageTemplateSelector x:Key="messageSelector"
ServerTemplate="{StaticResource serverClient}"
ClientTemplate="{StaticResource clientTemplate}"/>
DataTemplateSelector
public class MessageTemplateSelector : DataTemplateSelector
{
public DataTemplate ClientTemplate { get; set; }
public DataTemplate ServerTemplate { get; set; }
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
if (item.GetType() == typeof(ClientMessage))
return ClientTemplate;
return ServerTemplate;
}
}
First of all, as you can see I create two DataTemplate properties on my Selector, which I set it by XAML, doing this is easy to retrieve the DataTemplate, and finally I just compare the item received by parameter, which is the binded object(Message), and check its type and return the template.
And that is it, it works like expected.
UPDATE: Let's supposed that you want Template your items based on their types, just like my example, you could this the same thing above without using TemplateSelector, you could define the DataType of your Template:
<Window.Resources>
<DataTemplate DataType="{x:Type local:ClientMessage}">
<TextBlock Text="{Binding Content}"
Foreground="Red"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ServerMessage}">
<TextBlock Text="{Binding Content}"
Foreground="Green"/>
</DataTemplate>
</Window.Resources>
Doing so, the Template will be selected automatically according the type of the object.
I have a problem displaying a large dataset (around 27k elements). The problem is that after a certain point items would not display anymore but would still be be clickable. In fact, the items would display when scrolling but would disappear as soon as the scrolling stops. After some testing I found out it was stopping rendering after the 2^21 pixel. Complexity of the objects being rendered do not seem to affect this limit.
I have made a sample project to illustrate this:
In a blank windows store app, replace MainPage.xaml default grid with this one
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"></RowDefinition>
<RowDefinition Height="24"></RowDefinition>
<RowDefinition Height="24"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="textblock" Grid.Row="2"></TextBlock>
<Button Grid.Column="0" Grid.Row="1" Tapped="Button_Tapped"></Button>
<Button Grid.Column="1" Grid.Row="1" Tapped="Button_Tapped_1"></Button>
<Button Grid.Column="2" Grid.Row="1" Tapped="Button_Tapped_2"></Button>
<Button Grid.Column="3" Grid.Row="1" Tapped="Button_Tapped_3"></Button>
<ListView Grid.Column="0" Background="Red" x:Name="simpleList1" SelectionChanged="list_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,0,-8" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="100">
<TextBlock Text="{Binding Index}"></TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="1" Background="DarkGoldenrod" x:Name="simpleList2" SelectionChanged="list_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,0,-8" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="250">
<TextBlock Text="{Binding Index}"></TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="2" Background="Blue" x:Name="complexList1" SelectionChanged="list_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,0,-8" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="333">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding SimpleItem.Index}"></TextBlock>
<TextBlock Grid.Row="1" Text="{Binding ShortMsg}"></TextBlock>
<TextBlock Grid.Row="2" Text="{Binding RandomNumber}"></TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="3" Background="Violet" x:Name="complexList2" SelectionChanged="list_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,0,-8" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="500">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding SimpleItem.Index}"></TextBlock>
<TextBlock Grid.Row="1" Text="{Binding ShortMsg}"></TextBlock>
<TextBlock Grid.Row="2" Text="{Binding RandomNumber}"></TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
And in the MainPage.xaml.cs
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public class SimpleItem
{
public int Index { get; set; }
public SimpleItem(int index)
{
Index = index;
}
public override string ToString()
{
return "I am a simple item with index " + Index;
}
}
public class ComplexItem
{
static Random rand = new Random();
public SimpleItem SimpleItem { get; set; }
public string ShortMsg { get; set; }
public double RandomNumber { get; set; }
public ComplexItem(int index)
{
SimpleItem = new SimpleItem(index);
ShortMsg = "My simple item has index " + SimpleItem.Index;
RandomNumber = rand.NextDouble() * (double)SimpleItem.Index;
}
public override string ToString()
{
return "I am a complex item with index " + SimpleItem.Index;
}
}
public ObservableCollection<SimpleItem> listSI = new ObservableCollection<SimpleItem>();
public ObservableCollection<ComplexItem> listCI = new ObservableCollection<ComplexItem>();
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
InitializeLists();
simpleList1.ItemsSource = listSI;
simpleList2.ItemsSource = listSI;
complexList1.ItemsSource = listCI;
complexList2.ItemsSource = listCI;
}
private void InitializeLists()
{
for (int i = 0; i < 2000000; ++i)
{
listSI.Add(new SimpleItem(i));
listCI.Add(new ComplexItem(i));
}
}
private void Button_Tapped(object sender, TappedRoutedEventArgs e)
{
int target = (int)Math.Pow(2, 21);
int heigthPerItem = 100;
SimpleItem item = listSI[target / heigthPerItem];
simpleList1.ScrollIntoView(item);
}
private void Button_Tapped_1(object sender, TappedRoutedEventArgs e)
{
int target = (int)Math.Pow(2, 21);
int heigthPerItem = 250;
SimpleItem item = listSI[target / heigthPerItem];
simpleList2.ScrollIntoView(item);
}
private void Button_Tapped_2(object sender, TappedRoutedEventArgs e)
{
int target = (int)Math.Pow(2, 21);
int heigthPerItem = 333;
ComplexItem item = listCI[target / heigthPerItem];
complexList1.ScrollIntoView(item);
}
private void Button_Tapped_3(object sender, TappedRoutedEventArgs e)
{
int target = (int)Math.Pow(2, 21);
int heigthPerItem = 500;
ComplexItem item = listCI[target / heigthPerItem];
complexList2.ScrollIntoView(item);
}
private void list_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
string text = e.AddedItems[0].ToString();
textblock.Text = text;
}
}
}
This should be the result (with some info how to use it) : ListViewBugInterface
This person had the same problem as me and says it fixed it by using a VirtualizingStackPanel, but this shouldn't be needed since windows 8.1.
I doubt it is a UI virtualization problem since the ListViews scroll smoothly, the items start appearing when scrolling and can be tapped/clicked.
Anyone had this behavior? Am I missing the obvious? (e.g: to not have a dataset of 27k items) Anyone knows a solution to make the items render after the 2^21 pixel?
TLDR:
When displaying pixels after 2^21 pixels of combined ListViewItem they stop being rendered but can still be clicked. How do I fix this?
I am trying to implement a graph nodes something similar to the graph observed in http://audiomap.tuneglue.net/. (To get graph here Enter some text (ex: asd) in the search box. You will get a relationship graph.)
I could build the graph with a set of nodes and i can move them around. But the issue is I want to make fluid movement to other node while a node is dragged. I tried Expression Blend FluidMoveBehaviour. But it didn't fit perfectly like what i wanted.
Here is my XAML code:
<Window x:Class="SpicyNodes.MainWindow" Name="MainWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:SpicyNodes.ViewModels"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:SpicyNodes"
Title="MainWindow" Height="500" Width="500">
<Window.Resources>
<local:GraphItemDataTemplateSelector x:Key="graphItemDataTemplateSelector" ></local:GraphItemDataTemplateSelector>
<CompositeCollection x:Key="Col">
<CollectionContainer Collection="{Binding DataContext.Connectors,Source={x:Reference MainWindow1}}"/>
<CollectionContainer Collection="{Binding DataContext.Nodes,Source={x:Reference MainWindow1}}"/>
</CompositeCollection>
<DataTemplate x:Key="personTemplate" DataType="{x:Type viewModel:Person}">
<Thumb DragDelta="Thumb_Drag" DragCompleted="thumb_DragCompleted" DragStarted="thumb_DragStarted" Name="thumb"
>
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Canvas Margin="-10,-10,10,10">
<Ellipse Height="20" Width="20" Stroke="#696969" StrokeThickness="1" Fill="#353520"
x:Name="Ellipse">
</Ellipse>
<TextBlock Canvas.Top="-20" Canvas.Left="-40" Width="100" FontFamily="Cambria" FontSize="12"
TextAlignment="Center" Text="{Binding Name}"
IsHitTestVisible="False"
/>
</Canvas>
<ControlTemplate.Triggers>
<Trigger Property="IsDragging" Value="True">
<Setter TargetName="Ellipse" Property="Fill" Value="Green"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</DataTemplate>
<DataTemplate x:Key="connectorTemplate" DataType="{x:Type viewModel:Connector}">
<StackPanel>
<Line Stroke="Gray" StrokeThickness="2"
X1="{Binding StartNode.X}" Y1="{Binding StartNode.Y}"
X2="{Binding EndNode.X}" Y2="{Binding EndNode.Y}" x:Name="Line">
</Line>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"></RowDefinition>
<RowDefinition Height="0.2*"></RowDefinition>
</Grid.RowDefinitions>
<ItemsControl
ItemTemplateSelector="{StaticResource graphItemDataTemplateSelector}"
Name="items" ItemsSource="{StaticResource Col}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate x:Name="PanelTemplate">
<Canvas Name="canvas1" >
<!--<i:Interaction.Behaviors>
<ei:FluidMoveSetTagBehavior Tag="Element" AppliesTo="Children"/>
</i:Interaction.Behaviors>-->
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
<Button Grid.Row="1" Click="Button_Click" Height="30" Width="50">Add</Button>
</Grid>
And MainWindow.cs file contains:
void _CreateObjectOnCanvas(string Name, double X, double Y)
{
Person node2 = new Person(Name, "", "");
node2.Position = new Point(150, 120);
node2.X = X;
node2.Y = Y;
viewModel.Nodes.Add(node2);
Connector c = new Connector();
c.StartNode = viewModel.Nodes[0];
c.EndNode = node2;
viewModel.Connectors.Add(c);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Random rand = new Random();
var Angle = rand.Next(0, 360);
Angle = (Angle * 7) % 360;
var X = viewModel.Nodes[0].X + 100 * Math.Sin(Angle);
var Y = viewModel.Nodes[0].Y + 100 * Math.Cos(Angle);
if (!_CheckForOverlap(X, Y))
{
var Person = viewModel.CreatePerson(GIndex++);
_CreateObjectOnCanvas(Person.Name, X, Y);
}
}
Person class contains the following:
double x;
public override double X
{ get { return x; }
set
{
x = value;
OnPropertyChanged("X");
}
}
double y;
public override double Y
{
get { return y; }
set
{
y = value;
OnPropertyChanged("Y");
}
}
public string Name { get; set; }
public string Occupation { get; set; }
public string ContactInfo { get; set; }
public System.Windows.Point Position { get; set; }
public Person(string name, string occupation, string contact)
{
this.Name = name;
this.Occupation = occupation;
this.ContactInfo = contact;
}
Any help in this regard is greatly appreciated as i spent almost 2 days working on this.
Thank you.
This question is a continuation of the pregoing one.( How can I combine some UserControls in SilverLight?)
I have 3 view models with different colour properties.
How can I create elements of User Control with trigger invoke method after pressing the button on the element.
Here is a code of this element that I have upgrade with the trigger action.
<UserControl x:Class="SilverlightApplication14.NodePicture"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SilverlightApplication14"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<Grid x:Name="LayoutRootNodePicture" Height="100" Width="100"
HorizontalAlignment="Center">
<Canvas x:Name="ParentCanvas" Background="{Binding NodeColor}" Width="100" Height="100" >
</Canvas>
<Image HorizontalAlignment="Center"
Source="add.png"
Stretch="Fill"
Width="16"
VerticalAlignment="Top"
Margin="0,0,2,2"
Height="16" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<local:Add />
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
</Grid>
</UserControl>
And the code with the trigger action
namespace SilverlightApplication14
{
public class Add : TriggerAction<FrameworkElement>
{
protected override void Invoke(object parameter)
{
var vm = AssociatedObject.DataContext as NodeViewModel;
if (vm != null)
{
if (vm.Nodes == null)
{
vm.Nodes = new ObservableCollection<NodeViewModel>();
}
var child = new NodeViewModel { NodeColor = new SolidColorBrush(Color.FromArgb(255, 255, 0, 0)) };
vm.Nodes.Add(child);
}
}
}
}
Updated code:
<Grid>
<Grid.Resources>
<Style x:Key="myStyle" TargetType="ListBoxItem">
<Setter Property="Background" Value="Khaki" />
<Setter Property="Foreground" Value="DarkSlateGray" />
<Setter Property="Margin" Value="5" />
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="FontSize" Value="14" />
<Setter Property="BorderBrush" Value="DarkGray" />
</Style>
</Grid.Resources>
<ListBox ItemsSource="{Binding Nodes}" ItemContainerStyle="{StaticResource myStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<local:NodePicture DataContext="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
Is there a simple (or a right way ) way of doing this?
It is preferable to work with business-logic in view models, whereas triggers are intended for working with UI.
I would change the trigger to a command:
<Button Command="{Binding AddCommand}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Image ... />
</ControlTemplate>
</Button.Template>
</Button>
When a user clicks the button, the AddCommand is invoked. It can be implemented in the view model so:
public class NodeViewModel
{
public NodeViewModel()
{
this.AddCommand = new RelayCommand(obj => { /* do something */ });
}
public RelayCommand AddCommand { get; private set; }
//...
}
The RelayCommand class is one of the possible implementations and it can be downloaded with the MVVM Light framework here.