I have changed my view from simple Canvas to ItemsControl that uses Canvas, because I want to bind Canvas children to my ViewModel.
It was like this:
<Canvas x:Name="worksheetCanvas">
<local:BlockControl DataContext="{Binding x}"/>
<local:BlockControl DataContext="{Binding y}"/>
<local:BlockControl DataContext="{Binding z}"/>
</Canvas>
I "moved" step forward to MVVM and now I have this:
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Blocks}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="worksheetCanvas">
<!-- Here I have some attached properties defined -->
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding BlockTop}"/>
<Setter Property="Canvas.Left" Value="{Binding BlockLeft}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:BlockControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have to access Canvas
from code behind (I don't want pure MVVM, there will be some code behind). I have set x:Name
property for Canvas inside ItemsPanelTemplate, but it doesn't work:
Error CS0103 The name 'worksheetCanvas' does not exist in the current context
I guess this is because Canvas is created after compilation and cannot be accessed like this.
What is the best (efficient) way to get my Canvas reference in this scenario?
CodePudding user response:
You could create a derived ItemsControl (as a WPF custom control) with a Canvas as items host and a property that makes the Canvas accessible.
public class CanvasItemsControl : ItemsControl
{
static CanvasItemsControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(CanvasItemsControl),
new FrameworkPropertyMetadata(typeof(CanvasItemsControl)));
}
public Canvas Canvas { get; private set; }
public override void OnApplyTemplate()
{
Canvas = Template.FindName("Canvas", this) as Canvas;
}
}
Accessing the Canvas like this works with a default Style in Themes/Generic.xaml
as shown below. It does not set the ItemsPanel
property, but instead directly puts the hosting Canvas into the ControlTemplate of the ItemsControl.
<Style TargetType="{x:Type local:CanvasItemsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<Canvas x:Name="Canvas" IsItemsHost="True"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Your XAML would then look like this:
<local:CanvasItemsControl x:Name="itemsControl" ItemsSource="{Binding Blocks}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding BlockTop}"/>
<Setter Property="Canvas.Left" Value="{Binding BlockLeft}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:BlockControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</local:CanvasItemsControl>
As soon as the Template has been applied, you are able to access the Canvas
property, e.g. in a Loaded event handler of the Window:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
itemsControl.Canvas.Background = Brushes.AliceBlue;
}
CodePudding user response:
You can create UserControl
wrapper. And then access to canvas by Content
property
<ItemsPanelTemplate>
<local:MyCanvasWrapper>
<Canvas x:Name="worksheetCanvas">
<!-- Here I have some attached properties defined -->
</Canvas>
</local:MyCanvasWrapper>
</ItemsPanelTemplate>
Code behind
public partial class MyCanvasWrapper : UserControl // Or ContentControl
{
public MyCanvasWrapper()
{
InitializeComponent();
Loaded = (s, e) => {
var canvas = Content as Canvas;
}
}
}