itemscontrol in UWP doesnt bind to coordinates of observablecollection items [duplicate] - c#

This question already has answers here:
UWP Binding in Style Setter not working
(2 answers)
Closed 6 years ago.
My Code does not bind to the X and Y property of the items in the observable Collection. What is wrong:
<ItemsControl ItemsSource="{Binding LED}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="SkyBlue"/>
</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>
<Ellipse Stroke="{Binding Color}" Fill="{Binding FillColor}" StrokeThickness="1" Width="40" Height="40"></Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It does bind to Color and FillColor.
Here is the Shape class, that is stored in the ObservableCollection LED:
class Shape
{
public int X { get; private set; }
public int Y { get; private set; }
public string Color { get; private set; }
public string FillColor { get; private set; }
public Shape (int x, int y, string color, string fillColor)
{
X = x;
Y = y;
Color = color;
FillColor = fillColor;
}
}

The documentation for the Setter.Value property has the following note:
Windows Presentation Foundation (WPF) and Microsoft Silverlight supported the ability to use a Binding expression to supply the Value for a Setter in a Style. The Windows Runtime doesn't support a Binding usage for Setter.Value (the Binding won't evaluate and the Setter has no effect, you won't get errors, but you won't get the desired result either). When you convert XAML styles from WPF or Silverlight XAML, replace any Binding expression usages with strings or objects that set values, or refactor the values as shared {StaticResource} markup extension values rather than Binding-obtained values.
As a workaround, you could try using a RenderTransform instead:
<Ellipse Stroke="{Binding Color}" Fill="{Binding FillColor}" StrokeThickness="1" Width="40" Height="40">
<Ellipse.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Ellipse.RenderTransform>
</Ellipse>

Related

WPF Canvas elements all have a different perception of what Y means [duplicate]

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...

WPF: cannot set margin properly [duplicate]

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...

Hit-Testing in WPF for irregularly-shaped items

I have an irregularly shaped item (a line shape) contained within a ContentControl-derived class ("ShapeItem"). I style it with a custom cursor and I handle mouse-clicks within the ShapeItem class.
Unfortunately WPF thinks that the mouse is "over" my item if it is anywhere within the rectangular bounding box of the ContentControl. That's OK for closed shapes like a rect or circle, but it's a problem for a diagonal line. Consider this image with 3 such shapes on display and their bounding boxes shown in white:
Even if I am in the very bottom left hand corner of the bounding box around the line, it still shows the cursor and the mouse clicks still reach my custom item.
I want to change this so that that the mouse is only considered to be "over" the line detected if I am within a certain distance of it. Like, this region in red (forgive the crude drawing).
My question is, how do I approach this? Do I override some virtual "HitTest" related function on my ShapeItem?
I already know the math to figure out if I'm in the right place. I'm just wondering what approach is the best to choose. What functions do I override? Or what events do I handle, etc. I've gotten lost in the WPF documentation on Hit testing. Is it a matter of overriding HitTestCore or something like that?
Now for code. I host the items in a custom ItemsControl called "ShapesControl".
which uses the custom "ShapeItem" container to to host my view-model objects :
<Canvas x:Name="Scene" HorizontalAlignment="Left" VerticalAlignment="Top">
<gcs:ShapesControl x:Name="ShapesControl" Canvas.Left="0" Canvas.Top="0"
ItemsSource="{Binding Shapes}">
<gcs:ShapesControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" IsItemsHost="True" />
</ItemsPanelTemplate>
</gcs:ShapesControl.ItemsPanel>
<gcs:ShapesControl.ItemTemplate>
<DataTemplate DataType="{x:Type gcs:ShapeVm}">
<Path ClipToBounds="False"
Data="{Binding RelativeGeometry}"
Fill="Transparent"/>
</DataTemplate>
</gcs:ShapesControl.ItemTemplate>
<!-- Style the "ShapeItem" container that the ShapesControl wraps each ShapeVm ine -->
<gcs:ShapesControl.ShapeItemStyle>
<Style TargetType="{x:Type gcs:ShapeItem}"
d:DataContext="{d:DesignInstance {x:Type gcs:ShapeVm}}"
>
<!-- Use a custom cursor -->
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Cursor" Value="SizeAll"/>
<Setter Property="Canvas.Left" Value="{Binding Path=Left, Mode=OneWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Top, Mode=OneWay}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type gcs:ShapeItem}">
<Grid SnapsToDevicePixels="True" Background="{TemplateBinding Panel.Background}">
<!-- First draw the item (i.e. the ShapeVm) -->
<ContentPresenter x:Name="PART_Shape"
Content="{TemplateBinding ContentControl.Content}"
ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentControl.ContentTemplateSelector}"
ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}"
HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
IsHitTestVisible="False"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"
RenderTransformOrigin="{TemplateBinding ContentControl.RenderTransformOrigin}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</gcs:ShapesControl.ShapeItemStyle>
</gcs:ShapesControl>
</Canvas>
My "ShapesControl"
public class ShapesControl : ItemsControl
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is ShapeItem);
}
protected override DependencyObject GetContainerForItemOverride()
{
// Each item we display is wrapped in our own container: ShapeItem
// This override is how we enable that.
// Make sure that the new item gets any ItemTemplate or
// ItemTemplateSelector that might have been set on this ShapesControl.
return new ShapeItem
{
ContentTemplate = this.ItemTemplate,
ContentTemplateSelector = this.ItemTemplateSelector,
};
}
}
And my "ShapeItem"
/// <summary>
/// A ShapeItem is a ContentControl wrapper used by the ShapesControl to
/// manage the underlying ShapeVm. It is like the the item types used by
/// other ItemControls, including ListBox, ItemsControls, etc.
/// </summary>
[TemplatePart(Name="PART_Shape", Type=typeof(ContentPresenter))]
public class ShapeItem : ContentControl
{
private ShapeVm Shape => DataContext as ShapeVm;
static ShapeItem()
{
DefaultStyleKeyProperty.OverrideMetadata
(typeof(ShapeItem),
new FrameworkPropertyMetadata(typeof(ShapeItem)));
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
// Toggle selection when the left mouse button is hit
base.OnMouseLeftButtonDown(e);
ShapeVm.IsSelected = !ShapeVm.IsSelected;
e.Handled = true;
}
internal ShapesControl ParentSelector =>
ItemsControl.ItemsControlFromItemContainer(this) as ShapesControl;
}
The "ShapeVm" is just an abstract base class for my view models. Roughly this:
public abstract class ShapeVm : BaseVm, IShape
{
public virtual Geometry RelativeGeometry { get; }
public bool IsSelected { get; set; }
public double Top { get; set; }
public double Left { get; set; }
public double Width { get; }
public double Height { get; }
}
You could use a ShapeItem class like shown below. It is a Canvas with two Path children, one for hit testing and one for display. It resembles a few of the typical Shape properties (which you may extend according to your needs).
public class ShapeItem : Canvas
{
public ShapeItem()
{
var path = new Path
{
Stroke = Brushes.Transparent,
Fill = Brushes.Transparent
};
path.SetBinding(Path.DataProperty,
new Binding(nameof(Data)) { Source = this });
path.SetBinding(Shape.StrokeThicknessProperty,
new Binding(nameof(HitTestStrokeThickness)) { Source = this });
Children.Add(path);
path = new Path();
path.SetBinding(Path.DataProperty,
new Binding(nameof(Data)) { Source = this });
path.SetBinding(Shape.FillProperty,
new Binding(nameof(Fill)) { Source = this });
path.SetBinding(Shape.StrokeProperty,
new Binding(nameof(Stroke)) { Source = this });
path.SetBinding(Shape.StrokeThicknessProperty,
new Binding(nameof(StrokeThickness)) { Source = this });
Children.Add(path);
}
public static readonly DependencyProperty DataProperty =
Path.DataProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty FillProperty =
Shape.FillProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty StrokeProperty =
Shape.StrokeProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty StrokeThicknessProperty =
Shape.StrokeThicknessProperty.AddOwner(typeof(ShapeItem));
public static readonly DependencyProperty HitTestStrokeThicknessProperty =
DependencyProperty.Register(nameof(HitTestStrokeThickness), typeof(double), typeof(ShapeItem));
public Geometry Data
{
get => (Geometry)GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
public Brush Fill
{
get => (Brush)GetValue(FillProperty);
set => SetValue(FillProperty, value);
}
public Brush Stroke
{
get => (Brush)GetValue(StrokeProperty);
set => SetValue(StrokeProperty, value);
}
public double StrokeThickness
{
get => (double)GetValue(StrokeThicknessProperty);
set => SetValue(StrokeThicknessProperty, value);
}
public double HitTestStrokeThickness
{
get => (double)GetValue(HitTestStrokeThicknessProperty);
set => SetValue(HitTestStrokeThicknessProperty, value);
}
}
public class ShapeItemsControl : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ShapeItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is ShapeItem;
}
}
You would use it an XAML like this:
<gcs:ShapeItemsControl ItemsSource="{Binding Shapes}">
<gcs:ShapeItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</gcs:ShapeItemsControl.ItemsPanel>
<gcs:ShapeItemsControl.ItemContainerStyle>
<Style TargetType="gcs:ShapeItem">
<Setter Property="Data" Value="{Binding RelativeGeometry}"/>
<Setter Property="Fill" Value="AliceBlue"/>
<Setter Property="Stroke" Value="Yellow"/>
<Setter Property="StrokeThickness" Value="3"/>
<Setter Property="HitTestStrokeThickness" Value="15"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
</gcs:ShapeItemsControl.ItemContainerStyle>
</gcs:ShapeItemsControl>
However, you may not need a ShapeItem class and a derived ItemsControl at all, when you put the Canvas in the ItemTemplate of a regular ItemsControl:
<ItemsControl ItemsSource="{Binding Shapes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Cursor="Hand">
<Path Data="{Binding RelativeGeometry}" Fill="Transparent"
Stroke="Transparent" StrokeThickness="15"/>
<Path Data="{Binding RelativeGeometry}" Fill="AliceBlue"
Stroke="Yellow" StrokeThickness="3"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you also need to support selection, you should use a ListBox instead of an ItemsControl. A third Path in the ItemTemplate could visualize the selection state.
<ListBox ItemsSource="{Binding Shapes}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Template>
<ControlTemplate TargetType="ListBox">
<ItemsPresenter/>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Canvas Cursor="Hand">
<Path Data="{Binding RelativeGeometry}" Fill="Transparent"
Stroke="Transparent" StrokeThickness="15"/>
<Path Data="{Binding RelativeGeometry}"
Stroke="Green" StrokeThickness="7"
StrokeStartLineCap="Square" StrokeEndLineCap="Square"
Visibility="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListBoxItem},
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Path Data="{Binding RelativeGeometry}" Fill="AliceBlue"
Stroke="Yellow" StrokeThickness="3"/>
</Canvas>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Can WPF Canvas Children Bind ObservableCollection contain ViewModel for different shapes and TextBlocks?

I'm trying to create a WPF canvas control which can Draw shapes, curves, and textblocks, selecting and deleting them, so the best way I can think of this working is using MVVM binding a list of view models with Canvas Children.
However, Canvas Children can't be bind directly. So I'm kinda of stuck. Had look around internet but haven't found direct answers. I found someone suggesting the following way to achieve binding Canvas Children, but I couldn't understand what kind of type ViewModels "Nodes" are, and how to convert different Shapes, Curves and TextBlocks to Nodes.
Any help will be appreciated. Thanks in advance.
<ItemsControl ItemsSource="{Binding Path=Nodes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=XPos}" />
<Setter Property="Canvas.Top" Value="{Binding Path=YPos}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Your view model should contain a base class Node that defines the XPos and YPos properties, and derived classes for the specific node types, e.g. TextNode and ShapeNode:
public class Node
{
public double XPos { get; set; }
public double YPos { get; set; }
}
public class TextNode : Node
{
public string Text { get; set; }
}
public class ShapeNode : Node
{
public Geometry Geometry { get; set; }
public Brush Stroke { get; set; }
public Brush Fill { get; set; }
}
public class ViewModel
{
public ObservableCollection<Node> Nodes { get; } = new ObservableCollection<Node>();
}
In XAML, you would add DataTemplates for the specific nodes types like shown below. See the Data Templating Overview article on MSDN for details.
<ItemsControl ItemsSource="{Binding Path=Nodes}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:TextNode}">
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ShapeNode}">
<Path Data="{Binding Geometry}" Stroke="{Binding Stroke}" Fill="{Binding Fill}"/>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=XPos}" />
<Setter Property="Canvas.Top" Value="{Binding Path=YPos}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
You might now add different nodes to an instance of the main view model class and set the window's DataContext to that instance:
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
vm.Nodes.Add(new TextNode
{
XPos = 50,
YPos = 100,
Text = "Hello, World."
});
vm.Nodes.Add(new ShapeNode
{
XPos = 100,
YPos = 200,
Geometry = new EllipseGeometry { RadiusX = 50, RadiusY = 50 },
Fill = Brushes.Red
});
DataContext = vm;
}
If you want your view to react on property changes of the nodes, the Node class should implement the INotifyPropertyChanged interface.
If the items should be selectable, you should replace the ItemsControl by a ListBox. The TargetType of the ItemContainerStyle would then be ListBoxItem, and you would bind its IsSelected property to an appropriate property on your Node class.

Path Position Incorrect When Using Bindings To Populate Paths Data Attribute

I have a hard coded Path shape inside of a Canvas. I want to have multiple shapes inside this canvas so I'm trying to shove the information of each shape into a class and then use an ItemsControl to render each one. When I use the ItemsControl, each shape is in a incorrect location (Too far up and left).
Displays Correctly
<Canvas>
<Path Style="{StaticResource OverlayPath}"
Height="87.934"
Width="96.067"
Canvas.Left="348.456"
Canvas.Top="204.525"
Data="M432.9,245.5 L428.26666,258.46667 439.86716,261.26627 443.46698,246.46662 443.06664,242.33348 437.06651,242.60046 428.26633,239.13402 429.9994,232.33489 424.66584,230.73536 423.86545,234.46865 414.39771,236.46845 413.86433,239.66813 409.99697,236.73509 403.8631,235.80185 402.66265,233.66874 405.86266,231.13566 404.39584,224.73631 407.06279,221.93696 407.19614,217.00454 402.52898,211.00525 401.46255,204.73933 389.99435,207.00605 387.06071,211.4055 387.32706,222.20415 377.85934,219.93777 355.4564,218.33797 354.38926,226.20365 348.38853,227.80345 348.52187,233.93602 351.18886,239.53532 C351.18886,239.53532 356.12278,238.6021 355.72274,238.6021 355.32269,238.6021 361.99016,251.80045 361.99016,251.80045 L366.79074,253.53357 366.39069,258.5996 369.05768,259.13287 367.32414,268.73167 368.57429,275.93113 371.64132,279.19775 374.44166,279.73103 374.57501,286.46394 387.57658,287.19684 387.84328,290.06317 394.64409,291.66265 396.64434,285.79638 394.77744,284.99648 396.24429,279.99709 398.17785,279.13053 396.17761,276.99746 398.91128,274.99771 406.64554,277.86402 417.78022,267.79859 417.91357,262.53257 414.12144,259.02467 425.4228,249.8258 420.92226,244.72642 423.52258,243.02663 428.92323,242.82666 z" />
</Canvas>
Displays Incorrectly
<Canvas>
<ItemsControl ItemsSource={Binding CanvasPaths}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Style="{StaticResource OverlayPath}"
Data="{Binding Data}"
Height="{Binding Height}"
Width="{Binding Width}"
Canvas.Left="{Binding CanvasLeft}"
Canvas.Top="{Binding CanvasTop}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
Model
public class CanvasPath
{
public CanvasPath(string data, double height, double width, double canvasTop, double canvasLeft)
{
Data = data;
Height = height;
Width = width;
CanvasTop = canvasTop;
CanvasLeft = canvasLeft;
}
public string Data { get; set; }
public double Height { get; set; }
public double Width { get; set; }
public double CanvasTop { get; set; }
public double CanvasLeft { get; set; }
}
ViewModel
public class TestViewModel
{
private ObservableCollection<CanvasPath> test = new ObservableCollection<CanvasPath>()
{
new CanvasPath("M432.9,245.5 L428.26666,258.46667 439.86716,261.26627 443.46698,246.46662 443.06664,242.33348 437.06651,242.60046 428.26633,239.13402 429.9994,232.33489 424.66584,230.73536 423.86545,234.46865 414.39771,236.46845 413.86433,239.66813 409.99697,236.73509 403.8631,235.80185 402.66265,233.66874 405.86266,231.13566 404.39584,224.73631 407.06279,221.93696 407.19614,217.00454 402.52898,211.00525 401.46255,204.73933 389.99435,207.00605 387.06071,211.4055 387.32706,222.20415 377.85934,219.93777 355.4564,218.33797 354.38926,226.20365 348.38853,227.80345 348.52187,233.93602 351.18886,239.53532 C351.18886,239.53532 356.12278,238.6021 355.72274,238.6021 355.32269,238.6021 361.99016,251.80045 361.99016,251.80045 L366.79074,253.53357 366.39069,258.5996 369.05768,259.13287 367.32414,268.73167 368.57429,275.93113 371.64132,279.19775 374.44166,279.73103 374.57501,286.46394 387.57658,287.19684 387.84328,290.06317 394.64409,291.66265 396.64434,285.79638 394.77744,284.99648 396.24429,279.99709 398.17785,279.13053 396.17761,276.99746 398.91128,274.99771 406.64554,277.86402 417.78022,267.79859 417.91357,262.53257 414.12144,259.02467 425.4228,249.8258 420.92226,244.72642 423.52258,243.02663 428.92323,242.82666 z",
87.934, 96.067, 204.525, 348.456)
};
public ObservableCollection<CanvasPath> CanvasPaths
{
get
{
return test;
}
}
}
The Canvas.Left and Canvas.Top bindings in your ItemTemplate have no effect, because the Path control in the DataTemplate does not have a Canvas parent.
In order to make it work you would have to set the ItemsPanel and ItemContainerStyle properties like this:
<ItemsControl ItemsSource="{Binding CanvasPaths}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding CanvasLeft}"/>
<Setter Property="Canvas.Top" Value="{Binding CanvasTop}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Style="{StaticResource OverlayPath}"
Data="{Binding Data}"
Height="{Binding Height}"
Width="{Binding Width}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Categories