Add multiple XAML based WPF control to Canvas dyna

2019-08-23 22:19发布

As like the subject, I want to add WPF controls of the same template but different data inside as many as I want dynamically.

As you can see in the picture, the control that I want to duplicate is somewhat complicated. The overall control is wrapped inside Canvas inside ScrollViewer. Each StackPanel wraps TextBlock and another Canvas Control and this StackPanel is what I want to reproduce.

It is coded like:

<ScrollViewer x:Name="ScrollBoard" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
            <Canvas x:Name="CanvasBoard" VerticalAlignment="Center" HorizontalAlignment="Center" Width="200" Height="250" Background="Gray">

                <StackPanel x:Name="CanvasStack" Background="DimGray">
                    <CheckBox />

                    <Border x:Name="CanvasBorder" BorderBrush="Black" BorderThickness="1">
                        <Canvas Width="150" Height="200" ClipToBounds="True">

                            <Image x:Name="CanvasImage" Canvas.Left="0" Canvas.Top="0" Stretch="Fill" Source="C:\test.jpg"/>

                        </Canvas>
                    </Border>

                    <TextBlock Text="Test.jpg" />
                </StackPanel>

            </Canvas>
        </ScrollViewer>

Photo explanation

I want to duplicate that CanvasStack StackPanel control inside CanvasBoard Canvas Control.

Of course, not just duplicate but also want to take control of that. For example, change the position, edit TextBlock text, replace Image and get Click event and so on.

Plus, I will not use ListBox or ListView for it because each Control should be located in absolute x,y coordination with various size.

There are some examples doing similar things like 'adding a button to certain control'. But what I found were just add control in backend with hard-coded properties which may not fit for this kind of complex control.

Thank you in advance.

1条回答
放我归山
2楼-- · 2019-08-23 22:51

Whenever you're thinking of creating the same ui numerous times then you should think in terms of templating.

When you want properties of anything to change then you should think in terms of a datatemplate and bind those changing things to a public property of a viewmodel.

With repeated controls in the same area the first candidate should be some sort of an itemscontrol.

That has a stackpanel in it holds everything - but you can easily change that.

For numerous things on one canvas you can make the itemspanel of an itemscontrol a canvas.

You don't absolutely need a usercontrol to encapsulate your markup, you can just have a datatemplate.

Build a viewmodel for your windows. Build a viewmodel for each of these things you want to reproduce (vm).

Bind an observablecollection of vm to the itemssource of an itemscontrol.

Define a datatemplate associated with that type of viewmodel.

I work in c# so I'm likely to get something wrong if I try and write VB code. Run the code below through an online converter.

Markup:

<Window.DataContext>
    <local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
    <ItemsControl x:Name="ic" ItemsSource="{Binding Items}">
        <ItemsControl.Resources>
            <DataTemplate DataType="{x:Type local:StackyVM}">
                <StackPanel x:Name="CanvasStack" Background="DimGray">
                    <CheckBox />
                    <Border x:Name="CanvasBorder" BorderBrush="Black" BorderThickness="1">
                        <Canvas Width="150" Height="200" ClipToBounds="True">

                            <Image x:Name="CanvasImage" Canvas.Left="0" Canvas.Top="0" Stretch="Fill" 
                                   Source="{Binding ImageSource}"/>
                        </Canvas>
                    </Border>
                    <TextBlock Text="Test.jpg" />
                </StackPanel>
            </DataTemplate>
        </ItemsControl.Resources>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas Name="TheCanvas"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Top" Value="{Binding Top}"/>
                <Setter Property="Canvas.Left" Value="{Binding Left}"/>
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>

Viewmodel per stack

public class StackyVM : BaseViewModel
{
    private Double left;

    public Double Left
    {
        get { return left; }
        set
        {
            left = value;
            RaisePropertyChanged();
        }
    }

    private Double top;

    public Double Top
    {
        get { return top; }
        set { top = value; RaisePropertyChanged(); }
    }

    public string ImageUrl { get; set; }

}

Add any other properties which will vary for each stack and bind them. Instantiate one of those for each stack and add it to the bound observablecollection:

public class MainWindowViewModel : BaseViewModel
{
    public ObservableCollection<StackyVM> Items { get; set; }

Each will then be templated into a stack.

BaseViewModel implements inotifypropertychanged:

public  class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged([CallerMemberName] String propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
查看更多
登录 后发表回答