How to draw dynamic rectangles hierarchical? (MVVM-WPF) - c#

The Title explains my question I guess. I have one root rectangle which has children rectangles which can also have children rectangles. What would be the best way to draw all of them dynamically on a canvas?
My Rectangle-ViewModel:
public class SketchRectangleViewModel:ViewModelBase
{
public SketchRectangleViewModel(SketchRectangle sr)
{
_id = sr.Id;
_x = sr.x;
_y = sr.y;
_height = sr.Height;
_width = sr.Width;
_name = sr.Name;
_parentId = sr.ParentId;
}
private Guid _id;
private int _x;
private int _y;
private int _height;
private int _width;
private Guid _parentId;
private string _name;
private ObservableCollection<SketchRectangleViewModel> _children = new ObservableCollection<SketchRectangleViewModel>();
private bool _isSelected;
}

You could make a flat collection of all SketchRectangleViewModel objects in your view model:
How to flatten tree via LINQ?
...and bind the collection of all SketchRectangleViewModel objects to an ItemsControl:
<ItemsControl ItemsSource="{Binding YourCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="100" Height="100" />
</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.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Green" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note that you can only bind to public properties of the SketchRectangleViewModel class, so you need to turn your fields into properties:
public double X { get; set; }

Related

How to bind a TabControl with a collection of TabItems

I have a TabControl which has several TabItems. In each TabItem, there are a number of Buttons. A tabItem is stand for a room, and in the room there are several tables (Button)
I have bound one TabItem, but I am not sure how to bind lists of TabItems in a TabControl.
MainWindow:
<TabControl Grid.Row="0" Name="tabTables" Margin="-1 -5 -1 -1" Background="AliceBlue" BorderBrush="White">
<TabItem Visibility="Collapsed">
<ItemsControl ItemsSource="{Binding TableCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="canvas1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:TableViewModel}">
<Button Uid="{Binding TableName}" ContentStringFormat="{Binding TableGuestCount}" Style="{StaticResource ResourceKey=BtnTableEmpty}" Width="84" Height="90" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=TablePosX}" />
<Setter Property="Canvas.Top" Value="{Binding Path=TablePosY}" />
<Setter Property="Tag" Value="{Binding Path=TableID}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</TabItem>
</TabControl>
TableAreaViewModel:
public class TableAreaViewModel:BaseViewModel
{
public TableAreaViewModel()
{
TableCollection = new ObservableCollection<TableViewModel>();
}
public AreaViewModel Area { get; set; }
public ObservableCollection<TableViewModel> TableCollection { get; set;
}
}
AreaViewModel:
public class AreaViewModel: BaseViewModel
{
TableGrp _tbGrp = null;
public AreaViewModel(TableGrp tbGrp)
{
_tbGrp = tbGrp;
}
public string GrpID { get { return _tbGrp.GrpID; } }
public string GrpName { get { return _tbGrp.GrpName; } }
}
}
TableViewModel:
public class TableViewModel : BaseViewModel
{
private Table _table = null;
public TableViewModel(Table tb)
{
_table = tb;
}
public string TableID
{
get { return _table.TableID; }
set { _table.TableID = value; }
}
public string TableName
{
get { return _table.Name; }
set { _table.Name = value; }
}
public string TableGuestCount
{
get
{
int customerCnt = _table.CustCount ?? 0;
return string.Format("{0}/{1}", customerCnt, _table.MaxCount);
}
}
public double TablePosX
{
get { return _table.ShowXValue ?? 10; }
set { _table.ShowXValue = value; }
}
public double TablePosY
{
get { return _table.ShowYValue ?? 10; }
set { _table.ShowYValue = value; }
}
}
TableAreaListViewModel:
public class TableAreaListViewModel
{
public TableAreaListViewModel()
{
TableAreaCollection = new ObservableCollection<TableAreaViewModel>();
LoadTables();
}
public ObservableCollection<TableAreaViewModel> TableAreaCollection { get; set; }
private void LoadTables()
{
TableManager mgr = new TableManager();
var list = mgr.LoadTableAreas();
foreach (var tb in list)
{
TableAreaViewModel ta = new TableAreaViewModel();
ta.Area = new AreaViewModel(tb.TableGroup);
foreach(var item in tb.Tables )
{
ta.TableCollection.Add(new TableViewModel(item));
}
TableAreaCollection.Add(ta);
}
}
}
You would want to bind the ItemSource for the TabControl to you collection to generate the tabs. Something like this:
<TabControl ItemsSource="{Binding TableAreaCollection}">
<TabControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding TableCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="canvas1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:TableViewModel}">
<Button Uid="{Binding TableName}" ContentStringFormat="{Binding TableGuestCount}" Style="{StaticResource ResourceKey=BtnTableEmpty}" Width="84" Height="90" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=TablePosX}" />
<Setter Property="Canvas.Top" Value="{Binding Path=TablePosY}" />
<Setter Property="Tag" Value="{Binding Path=TableID}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>

Why do not bind to the properties Width and Height of Canvas?

Why do not bind to the properties Width and Height of Canvas?
I tried to do it like that, but it did not .
<ItemsControl
Grid.Row="0"
ItemsSource="{Binding RectItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
//does not work
<Canvas Height="{Binding PanelHeight, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="{Binding PanelWidth, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</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.ItemTemplate>
<DataTemplate>
<Rectangle
Width="{Binding Width}"
Height="{Binding Height}"
Fill="Black" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Those bindings seem to work just fine; just add
Background="Red"
to the definition of Canvas so that you can SEE the actual Canvas.
I've made you an example:
Classes
public class RectItem : INotifyPropertyChanged
{
private int _width;
private int _height;
private int _x;
private int _y;
public Brush Color { get; set; }
public int Width {
get { return _width; }
set {
if (value == _width) return;
_width = value;
OnPropertyChanged();
}
}
public int Height {
get { return _height; }
set {
if (value == _height) return;
_height = value;
OnPropertyChanged();
}
}
public int X {
get { return _x; }
set {
if (value == _x) return;
_x = value;
OnPropertyChanged();
}
}
public int Y {
get { return _y; }
set {
if (value == _y) return;
_y = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
internal class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.RectItems.Add(new RectItem { Height = 100, Width = 100, X = 0, Y = 0, Color = Brushes.DeepPink });
this.RectItems.Add(new RectItem { Height = 50, Width = 50, X = 100, Y = 100, Color = Brushes.DeepSkyBlue });
}
private double _panelWidth = 100;
private double _panelHeight = 100;
public ObservableCollection<RectItem> RectItems { get; } = new ObservableCollection<RectItem>();
public ICommand IncreaseSizeCommand => new RelayCommand(x =>
{
this.PanelHeight = 200;
this.PanelWidth = 200;
});
public double PanelWidth {
get { return _panelWidth; }
set {
if (value.Equals(_panelWidth)) return;
_panelWidth = value;
OnPropertyChanged();
}
}
public double PanelHeight {
get { return _panelHeight; }
set {
if (value.Equals(_panelHeight)) return;
_panelHeight = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML
<StackPanel>
<Button Content="Click me" Width="80" Height="20" Command="{Binding IncreaseSizeCommand}"/>
<ItemsControl
Grid.Row="0"
ItemsSource="{Binding RectItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Height="{Binding PanelHeight, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="{Binding PanelWidth, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</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.ItemTemplate>
<DataTemplate>
<Rectangle
Width="{Binding Width}"
Height="{Binding Height}"
Fill="{Binding Color}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Works as aspected. I've added some colors to do some visual highlighting and a command to change the size of the Canvas

How to draw grid lines that scale?

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.

Add n rectangles to canvas with MVVM in WPF

I want to add a set of rectangles to the main window of my mvvm application. In my viewModel I've got a collection of objects which I convert to System.Windows.Shapes.Rectangle classes with a converter (code below):
ViewModel:
RecognizedValueViewModel
{
public ObservableCollection<BarcodeElement> BarcodeElements
{
get { return _BarcodeElements; }
set { _BarcodeElements = value; }
}
public RecognizedValueViewModel()
{
BarcodeElements = InitializeBarcodeElements();
}
}
Converter:
public BarcodeElementToRectangleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Rectangle barcodeRectangle = GetRectangleFromBarcodeElement(value as BarcodeElement);
return barcodeRectangle;
}
}
The rectangles should be shown in a canvas in my MainWindow:
<Canvas x:Name="Canvas_Image_Main">
<!-- Show rectangles here -->
</Canvas>
I would add Rectangles to canvas in code but I don't now how many rectangles are there at runtime. Is there a way how I can achieve this? Tank you.
In a proper MVVM approach you would have a view model with an abstract representation of a list of rectangles, e.g. like this:
public class RectItem
{
public double X { get; set; }
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; }
}
public class ViewModel
{
public ObservableCollection<RectItem> RectItems { get; set; }
}
Then you would have a view that uses an ItemsControl to visualize a collection of such Rect items. The ItemsControl would have a Canvas as its ItemsPanel and an appropriate ItemContainerStyle and ItemTemplate which each bind to the appropriate view model properties. It might look like this:
<ItemsControl ItemsSource="{Binding RectItems}">
<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.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Black"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
An alternative without Bindings in Style Setters (which don't work in UWP) might look like this:
<ItemsControl ItemsSource="{Binding RectItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Black">
<Rectangle.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can bind the collection of rectangles to an ItemControl and set its height, width and margin:
<ItemsControl ItemsSource="{Binding Path=RectangleCollection,Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Canvas>
<Rectangle Stroke="Black" Heigth={some converter} Width={some converter} Margin={Some Converter}>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemControl>
Just an idea to get you started...

Fluid like movement to the items in ItemsControl

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.

Categories

Resources