Home > Back-end >  How to access Canvas instance when it is in ItemsPanel?
How to access Canvas instance when it is in ItemsPanel?

Time:08-24

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;
        }
    }
}
  • Related