首页 > 解决方案 > 在 VB.Net 中动态添加多个基于 XAML 的 WPF 控件到 Canvas

问题描述

与主题一样,我想动态添加相同模板但不同数据的 WPF 控件。

正如您在图片中看到的,我要复制的控件有些复杂。整体控制被包裹在Canvas里面ScrollViewer。每个StackPanel包装TextBlockanother画布控制,这StackPanel就是我想要重现的。

它的编码如下:

<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>

图片说明

我想在 CanvasBoard Control 中复制CanvasStack控件StackPanel Canvas

当然,不只是重复,还想控制它。比如改变位置、编辑TextBlock文本、替换Image和获取点击事件等等。

另外,我不会使用它ListBoxListView因为每个控件都应该位于绝对 x,y 坐标中,具有不同的大小。

有一些示例执行类似的操作,例如“将按钮添加到某些控件”。但是我发现只是在后端添加了具有硬编码属性的控件,这可能不适合这种复杂的控件。

先感谢您。

标签: c#wpfvb.netvisual-studiocontrols

解决方案


每当您考虑多次创建相同的用户界面时,您应该考虑模板。

当您想要更改任何内容的属性时,您应该考虑数据模板并将这些更改的内容绑定到视图模型的公共属性。

在同一区域中重复控件时,第一个候选对象应该是某种项目控件。

里面有一个堆栈面板,里面有所有东西——但你可以很容易地改变它。

对于一个画布上的许多东西,您可以使项目的项目面板控制一个画布。

您并不绝对需要一个用户控件来封装您的标记,您可以只拥有一个数据模板。

为您的窗口构建视图模型。为您想要重现的每一件事(vm)构建一个视图模型。

将 vm 的 observablecollection 绑定到 itemscontrol 的 itemssource。

定义与该类型视图模型关联的数据模板。

我在 c# 中工作,所以如果我尝试编写 VB 代码,我可能会出错。通过在线转换器运行以下代码。

标记:

<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>

每个堆栈的视图模型

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

}

添加任何其他因堆栈而异的属性并绑定它们。为每个堆栈实例化其中一个并将其添加到绑定的 observablecollection:

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

然后每个都将被模板化到一个堆栈中。

BaseViewModel 实现 inotifypropertychanged:

public  class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged([CallerMemberName] String propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

推荐阅读