Reputation: 13941
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?
Upvotes: 0
Views: 397
Reputation: 128146
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()
{
base.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;
}
Upvotes: 1
Reputation: 169400
You could use the VisualTreeHelper
class to find the Canvas
in the visual tree once the ItemsControl
has been loaded, e.g.:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Canvas worksheetCanvas = FindVisualChild<Canvas>(itemsControl);
//...
}
private static childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
{
return (childItem)child;
}
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
}
Upvotes: 1
Reputation: 1
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;
}
}
}
Upvotes: -1