Kamil
Kamil

Reputation: 13941

How to access Canvas instance when it is in ItemsPanel?

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

Answers (3)

Clemens
Clemens

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

mm8
mm8

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

FlashMX2004
FlashMX2004

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

Related Questions