Canvas with static content and items - wpf

I have a Canvas which contains static elements - those elements use binding to draw it in the right places. Now I need to draw other elements depending on items in a collection. I want to use ItemsControl, but I don't know how to do it correctly. My current pseudo-code:
<UserControl.Resources>
<RectangleGeometry x:Key="MyGeometry1">
<RectangleGeometry.Rect>
<MultiBinding Converter="{StaticResource RectConverter}">
<Binding Path="ActualWidth" ElementName="m_Canvas" />
<Binding Path="ActualHeight" ElementName="m_Canvas" />
</MultiBinding>
</RectangleGeometry.Rect>
</RectangleGeometry>
</UserControl.Resources>
<Canvas x:Name="m_Canvas">
<!-- "static" content -->
<Line x:Name="Line1" X1="{Binding Line1X1}" X2="{Binding Line1X2}" Y1="{Binding Line1Y1}" Y2="{Binding Line1Y2}"/>
<Line X1="0" X2="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Canvas}}}" Y1="0" Y2="0">
<Path Fill="#35A500"
Opacity="0.15">
<Path.Data>
<GeometryGroup FillRule="EvenOdd">
<StaticResource ResourceKey="MyGeometry1" />
</GeometryGroup>
</Path.Data>
</Path>
<Line X1="{Binding Items[0].X1}" X2="{Binding Items[0].X2}" Y1="{Binding Items[0].Y1}" Y2="{Binding Items[0].Y2}"/>
<Line X1="{Binding Items[1].X1}" X2="{Binding Items[1].X2}" Y1="{Binding Items[1].Y1}" Y2="{Binding Items[1].Y2}"/>
<Line X1="{Binding Items[2].X1}" X2="{Binding Items[2].X2}" Y1="{Binding Items[2].Y1}" Y2="{Binding Items[2].Y2}"/>
</Canvas>
I tried something like this:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<!-- "static" content -->
<Line x:Name="Line1" X1="{Binding Line1X1}" X2="{Binding Line1X2}" Y1="{Binding Line1Y1}" Y2="{Binding Line1Y2}"/>
<Line X1="0" X2="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Canvas}}}" Y1="0" Y2="0">
</Canvas>
</ItemsPanelTemplate>
<!-- (...) other code -->
</ItemsControl>
But I have an error:
Error XDG0062 Cannot explicitly modify Children collection of Panel used as ItemsPanel for ItemsControl. ItemsControl generates child elements for Panel.
I understand that is because I put Line1 inside Canvas but how to make such Canvas with some kind static content and also as a container for items?

The "static content" could be put into another Canvas above or below the ItemsPresenter in the ControlTemplate of the ItemsControl:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<Grid>
<Canvas>
<!-- "static" content -->
</Canvas>
<ItemsPresenter/>
</Grid>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

Related

WPF/XAML: Positioning primitives in nested data-structure within a canvas

in my current Project I have a nested data-structure. The root is an ObservableCollection ( terrainModel.terrainElements). Each of the elements in the collection host another ObservableCollection(drawElements) that consists of data to draw primitives in a canvas. Depending on the primitive I provide a DataTemplate so that it is rendered in the Canvas.
Here is the actual XAML:
<ItemsControl ItemsSource="{Binding terrainModel.terrainElements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="1000" Height="500" Background="Aquamarine"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding drawElements}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type drawtype:LineElement}">
<Line Stroke="Black" X1="{Binding startPoint.X}" Y1="{Binding startPoint.Y}" X2="{Binding endPoint.X}" Y2="{Binding endPoint.Y}" />
</DataTemplate>
<DataTemplate DataType="{x:Type drawtype:CircleElement}">
<Ellipse Stroke="Black" Width="{Binding radius}" Height="{Binding radius}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type drawtype:RectangleElement}">
<Rectangle Stroke="Blue" Width="{Binding width}" Height="{Binding height}" Canvas.Left="{Binding position.X}" Canvas.Top="{Binding position.Y}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The problem is however that I am not able to set the position of the primitives correct in the XAML, as Canvas.Left="{Binding position.X}" for example does not work as it is in the inner ItemsControl and not in the outer ItemsControl.
I also tried to transform the primitives like this:
<Rectangle.RenderTransform><TranslateTransform x:Name="myTransform2" X="{Binding position.X}" Y="{Binding position.Y}" /></Rectangle.RenderTransform>
This worked but ruined the position of following elements to draw. Of course I could draw everything in code of the view, but I would like to know if it's also possible to do in xaml.
The solution by Clemens worked:
Set a Canvas as the ItemsPanel for the inner ItemsControl the same way
you did for the outer one. Then use the RenderTransform solution, as
setting Canvas.Left and Canvas.Top would still not work because the
Rectangle will not become a direct child of the Canvas. You would need
to set an ItemsContainerStyle for the Left and Top bindings.
The outer Canvas should still (also) have a Canvas as its ItemsPanel.
Otherwise you will have problems with more than one terrainElement.
I thought that a new Canvas would be generated in the inner ItemsControl for every outer ItemsControl.
Here is the XAML if somebody is interested:
<ItemsControl ItemsSource="{Binding terrainModel.terrainElements}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding drawElements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="1000" Height="500" Background="Aquamarine"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type drawtype:LineElement}">
<Line Stroke="Black" X1="{Binding startPoint.X}" Y1="{Binding startPoint.Y}" X2="{Binding endPoint.X}" Y2="{Binding endPoint.Y}" />
</DataTemplate>
<DataTemplate DataType="{x:Type drawtype:CircleElement}">
<Ellipse Stroke="Black" Width="{Binding radius}" Height="{Binding radius}">
<Ellipse.RenderTransform>
<TranslateTransform x:Name="myTransform" X="{Binding position.X}" Y="{Binding position.Y}" />
</Ellipse.RenderTransform>
</Ellipse>
</DataTemplate>
<DataTemplate DataType="{x:Type drawtype:RectangleElement}">
<Rectangle Stroke="Blue" Width="{Binding width}" Height="{Binding height}">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="myTransform2" X="{Binding position.X}" Y="{Binding position.Y}" />
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

How to fit a list of paths inside a border in WPF

am writing a C# WPF program to view and edit SVGs.
So i have a List of pathes (PathList) and i diplay them inside a border on a canvas here's my XAML:
<ItemsControl ItemsSource="{Binding PathList}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown" >
<cmd:EventToCommand Command="{Binding Path= OnMouseDownCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Viewbox Stretch="Uniform" Height="500" Width="500">
<Path Stroke="{Binding Stroke}"
StrokeThickness="{Binding StrokeThickness}"
Fill="{Binding Fill}"
Data="{Binding Data}"
Tag="{Binding Tag}"
</Viewbox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
as i said in the title my goal is to fit those paths inside a border and as you can see in my code i used Viewbox to stretch the paths but the problem is it stretches each and every path inside the list to fill the size (500*500) distorting the form of the vector graphics.. i want the scaling to happen to all the list at once as an entity leaving the relative scale and locations of those paths intact.
The following simplified example shows how to put a list of Geometry items in a scalable ItemsControl. It is necessary that you set the Width and Height of the ItemsControl.
<Viewbox>
<ItemsControl Width="500" Height="500">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Stroke="Black" StrokeThickness="3" Data="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Items>
<EllipseGeometry Center="150,150" RadiusX="100" RadiusY="100"/>
<EllipseGeometry Center="350,350" RadiusX="100" RadiusY="100"/>
</ItemsControl.Items>
</ItemsControl>
</Viewbox>

Adding text to WPF Line

How do I add text to a Line UIElement? I would like to have the text placed in the middle of the line.
<Line Stroke="Black" X1="{Binding From.CanvasCenterX}" Y1="{Binding From.CanvasCenterY}" X2="{Binding To.CanvasCenterX}" Y2="{Binding To.CanvasCenterY}" StrokeThickness="2" />
Is this possible?
The following XAML code adds text to a Line UIElement. In this example the text is presented by a <TextBlock... />. The text is centered at the Line, but this can easily be changed by the TextAlignment property.
<Grid>
<TextBlock Text="{Binding RelationName}" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Line Stroke="Black" X1="{Binding From.CanvasCenterX}" Y1="{Binding From.CanvasCenterY}" X2="{Binding To.CanvasCenterX}" Y2="{Binding To.CanvasCenterY}" StrokeThickness="2" />
</Grid>
VerticalAlignment and HorizontalAlignment places the <TextBlock../> in the <Grid../>
You need to set X2 coordinate value based on length of Text for alignment.
<Line Stroke="Black"
X1="{Binding From.CanvasCenterX}" Y1="{Binding From.CanvasCenterY}"
X2="{Binding To.CanvasCenterX}" Y2="{Binding To.CanvasCenterY}" StrokeThickness="2" />
<TextBlock Text="Line Between the Text!"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Line Stroke="Black"
X1="{Binding From.CanvasCenterX}" Y1="{Binding From.CanvasCenterY}"
X2="{Binding To.CanvasCenterX}" Y2="{Binding To.CanvasCenterY}" StrokeThickness="2" />

Line is not drawn from a textblock to another textblock that is within another canvas

<StackPanel Margin="20">
<Line StrokeDashArray="4 4" Stroke="Red" X1="0" Y1="0" X2="{Binding LeftPoint}" Y2="{Binding TopPoint}"></Line>
<TextBlock Text="Hello"></TextBlock>
<Border Height="100"></Border>
<Canvas Name="B" >
<Border Height="48"></Border>
<TextBlock Canvas.Left="{Binding LeftPoint, Mode=TwoWay}" Canvas.Top="{Binding TopPoint, Mode=TwoWay}" Text="World"></TextBlock>
</Canvas>
</StackPanel>
Above is the code sample, line is not drawn from hello to world. I would like to preserve the structure and still be able to draw the line.
Please suggest.
I guess you're missing the StrokeThickness:
<Line StrokeDashArray="4 4"
Stroke="Red" StrokeThickness="1"
X1="0" X2="{Binding LeftPoint}"
Y1="0" Y2="{Binding TopPoint}"/>

WPF : get width of span dynamically

i tried do some content in FlowDocument to be highlighten by rectangle. like following code:
<FlowDocument>
<Paragraph>
<Span>
here is a span.
<Span.Background>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle Fill="Gray" RadiusX="5" RadiusY="5" Width="100" Height="50"/>
</VisualBrush.Visual>
</VisualBrush>
</Span.Background>
</Span>
</Paragraph>
<FlowDocument>
i want to set rectangle's width and height to span.
how can i get span's actual width and height which determined by span's content's length?
added:
it dosen't work. (it occured System.InvalidOperationException in design time)
<FlowDocument>
<Paragraph>
<Span>
Here is a span.
<Span.Background>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle Fill="Gray" RadiusX="5" RadiusY="5" Height="50" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Span}}, Path=Width}"/>
</VisualBrush.Visual>
</VisualBrush>
</Span.Background>
</Span>
</Paragraph>
</FlowDocument>
Try something like this
Width="{Binding RelativeSource=
{RelativeSource FindAncestor,
AncestorType={x:Type Span}},
Path=ActualWidth}"
>
A converter can be usefull on this binding for proportions, tell me if you need a converter example also.

Resources