【问题标题】:Adding views dynamically and arranging in grid动态添加视图并在网格中排列
【发布时间】:2019-12-02 13:00:39
【问题描述】:

我正在使用 WPF 构建桌面应用程序,我需要能够以一种看起来井井有条的方式动态添加视图。

我创建了一个ObservableCollection,并将itemsControl.ItemsSource 设置为该集合。

我可以添加视图,只是看起来很糟糕。

关于可以添加的视图数量,我所知道的是最多可以有 16 个视图。

我考虑过创建一个动态网格,它会根据可用视图的数量而变化,如下图所示

所以我尝试采用动态网格的想法, 每次要添加/删除视图(听起来很糟糕)时,我都会创建一个新网格,并且根据视图集合的大小,我知道如何正确拆分网格。

然后我遍历集合中的所有现有视图,并通过正确的逻辑将它们放置在网格上。我在这里弄得一团糟,所以如果我完全走错了方向,请告诉我。

代码如下:

public partial class DynamicTargetView : UserControl
{
    private ObservableCollection<TargetView> views = new ObservableCollection<TargetView>();
    public Grid grid;

    public DynamicTargetView()
    {
        InitializeComponent();
        SettingsBar.onViewChange += addOrRemoveTargetsFromPanel;

    }


    public void addOrRemoveTargetsFromPanel(Object sender, WeiTargetGui.EventArgs.AddOrRemoveViewEventArgs e)
    {
        if (e.isShown)
        {
            addTargetToPanel(e.id);
        }
        else removeTargetFromPanel(e.id);
    }

    public void addTargetToPanel(string id)
    {
        views.Add(new TargetView(Int32.Parse(id)));

        ArrangeGrid();
    }

    public void removeTargetFromPanel(string id)
    {
        foreach (TargetView v in views)
        {
            if (v.id == Int32.Parse(id))
                views.Remove(v);
        }
        ArrangeGrid();
    }
    public void ArrangeGrid()
    {
        int NumOfViews = views.Count();
        grid = new Grid();
        grid.Children.Clear();

        ColumnDefinition gridCol1;
        ColumnDefinition gridCol2;
        ColumnDefinition gridCol3;
        ColumnDefinition gridCol4;
        RowDefinition gridRow1;
        RowDefinition gridRow2;
        RowDefinition gridRow3;
        RowDefinition gridRow4;

        if (NumOfViews == 1)
        {
            addtoGrid(0, 0);
        }
        else if (NumOfViews == 2)
        {
            gridCol1 = new ColumnDefinition();
            gridCol2 = new ColumnDefinition();
            grid.ColumnDefinitions.Add(gridCol1);
            grid.ColumnDefinitions.Add(gridCol2);
            addtoGrid(2, 0);
        }
        else if (NumOfViews < 5)
        {
            gridCol1 = new ColumnDefinition();
            gridCol2 = new ColumnDefinition();
            gridRow1 = new RowDefinition();
            gridRow2 = new RowDefinition();

            grid.ColumnDefinitions.Add(gridCol1);
            grid.ColumnDefinitions.Add(gridCol2);
            grid.RowDefinitions.Add(gridRow1);
            grid.RowDefinitions.Add(gridRow2);
            addtoGrid(2, 2);
        }
        else if (NumOfViews < 10)
        {
            gridCol1 = new ColumnDefinition();
            gridCol2 = new ColumnDefinition();
            gridCol3 = new ColumnDefinition();
            gridRow1 = new RowDefinition();
            gridRow2 = new RowDefinition();
            gridRow3 = new RowDefinition();
            grid.ColumnDefinitions.Add(gridCol1);
            grid.ColumnDefinitions.Add(gridCol2);
            grid.ColumnDefinitions.Add(gridCol3);
            grid.RowDefinitions.Add(gridRow1);
            grid.RowDefinitions.Add(gridRow2);
            grid.RowDefinitions.Add(gridRow3);

            addtoGrid(3, 3);
        }
        else if (NumOfViews < 17)
        {
            gridCol1 = new ColumnDefinition();
            gridCol2 = new ColumnDefinition();
            gridCol3 = new ColumnDefinition();
            gridCol4 = new ColumnDefinition();
            gridRow1 = new RowDefinition();
            gridRow2 = new RowDefinition();
            gridRow3 = new RowDefinition();
            gridRow4 = new RowDefinition();

            grid.ColumnDefinitions.Add(gridCol1);
            grid.ColumnDefinitions.Add(gridCol2);
            grid.ColumnDefinitions.Add(gridCol3);
            grid.ColumnDefinitions.Add(gridCol4);
            grid.RowDefinitions.Add(gridRow1);
            grid.RowDefinitions.Add(gridRow2);
            grid.RowDefinitions.Add(gridRow3);
            grid.RowDefinitions.Add(gridRow4);

            addtoGrid(4, 4);
        }


    }

    public void addtoGrid(int cols, int rows)
    {
        int row = 0;
        int column = 0;

        foreach (var view in views)
        {
            if (cols == 0 && rows == 0)
            {
                grid.Children.Add(view);
                break;
            }
            if (cols == 2 && rows == 0)
            {
                Grid.SetColumn(view, column);
                column++;
                grid.Children.Add(view);
            }
            else
            {
                if (column != cols)
                {
                    Grid.SetColumn(view, column);
                    Grid.SetRow(view, row);
                    grid.Children.Add(view);
                }
                if (column < cols)
                    column++;
                else
                {
                    column = 0;
                    row++;
                }

            }

        }
       this.Content = grid;
    }

}

TargetView 是一个用户控件,它代表一个带有一些关于该目标的数据的目标。

所以这里是代码的问题-

1) 在视图集合中添加多个组件时,出现以下异常:

System.InvalidOperationException: '指定元素已经是另一个元素的逻辑子元素。先断开它。'

我已经添加了grid.Children.Clear() - 没有任何帮助。

2) 最后一个问题 - 代码看起来很糟糕,我只想让它工作,这样我下次可以更好地学习和实现它。

我们将不胜感激,非常感谢。

【问题讨论】:

    标签: c# wpf dynamic-view


    【解决方案1】:

    你完全错了。如果你只是做正确的事情并使用数据绑定,如果不是成熟的 MVVM,你会省去很多麻烦。

    当您想在 WPF 中显示“事物”(视图或其他任何内容)列表时,您应该使用绑定到包含您要创建的视图逻辑的项目集合的 ItemsSource。当你这样做时,你会看到一个普通的 ListBox,每个元素都是一行包含类名的文本。诀窍是然后使用 DataTemplates 指定为每种后端类型创建的视图类型,即:

    <DataTemplate TargetType="{x:Type local:MyViewModelTypeA}">
        <view:MyViewTypeA />
    </DataTemplate>
    
    <DataTemplate TargetType="{x:Type local:MyViewModelTypeB}">
        <view:MyViewTypeB />
    </DataTemplate>
    

    执行此操作,您的视图现在看起来正确,但它们仍将位于垂直 StackPanel 中。要更改它,您可以替换默认的 ItemsPanel:

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    

    如果您想要更“类似于网格”的东西,那么您也可以使用 UniformGrid,并将其 NumColumns 绑定到您的父类中的一个属性,该属性在运行时计算此值。

    这方面有很多变化,需要调整许多不同的参数,但这是大体思路。 WPF 的关键是在您的 DataContext 类中执行尽可能多的逻辑,并使用尽可能轻量级的绑定将您的前端绑定到它。

    【讨论】:

    • 所以如果我正确理解了你的答案,在我的情况下MyViewModelTypeA 将是可观察的集合? MyViewTypeA 将是 TargetView (用户控件)?我问是因为所有视图都来自同一类型。所以实际上我只需要在我的 xaml 中“声明”一次。对吗?
    • MyViewTypeA 是您的目标视图,但 MyViewModelTypeA 是您创建的包含每个 MyViewTypeA 实例需要显示的属性的类。然后,您的父类维护一个属性 ObservableCollection&lt;MyViewModelTypeA&gt; MyItems {get; set;},并且您的 ItemsControls 使用 ItemsSource="{Binding MyItems}" 绑定到它。
    • This link 更详细地展示了我所说的内容,实际上它可能更适合您的情况,因为您只有一个视图类型(我建议使用 DataTemplates 的唯一原因是因为这就是你做多种类型的方式)。他直接分配 ItemsSource(而不是绑定声明),但原理相同……他的 TodoItem 类是所有视图都绑定到的“视图模型”。
    • 非常感谢!明白了。
    【解决方案2】:

    如果元素大小相同,则使用 UniformGrid 而不是 Grid,这样可以避免不必要的计算

    wpfPanels

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-29
      • 1970-01-01
      • 1970-01-01
      • 2014-07-15
      • 2019-09-22
      • 2021-11-07
      • 2022-08-17
      • 1970-01-01
      相关资源
      最近更新 更多