wpf 在 C# 中向 UI 动态添加元素
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24728667/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Dynamically adding elements to a UI in C#
提问by AtomicStrongForce
The Problem
问题
I have a C# window with some text fields and buttons on it. It starts out similar to this:

我有一个 C# 窗口,上面有一些文本字段和按钮。它开始类似于这样:

When the user clicks that "+ Add Machine Function" button, I need to create a new row of controls and move the button below those:

当用户单击“+ 添加机器功能”按钮时,我需要创建一个新的控件行并将按钮移动到这些控件下方:

If the user clicks "+Add Scale Unit" the program needs to add some controls to the right:

如果用户点击“+Add Scale Unit”,程序需要在右侧添加一些控件:

Attempts at a solution
解决方案的尝试
I have tried using Windows Forms' TableLayoutPanel but it seemed to handle resizing itself to fit additional controls in odd ways, for example it would some one rows of controls much wider than the others, and would make some rows so short it cut off parts of my controls.
我曾尝试使用 Windows 窗体的 TableLayoutPanel,但它似乎以奇怪的方式调整自身大小以适应其他控件,例如,它会使一些控件行比其他控件宽得多,并且会使某些行如此短以至于切断部分控件我的控件。
I have also tried simply placing the controls by themselves into the form by simply calculating their relative positions. However I feel that this is bad programming practice as it makes the layout of the form relatively hard to change later. In the case of the user deleting the row or scale unit by pressing the 'X' beside it, this method also requires the program to find each element below that one and move it up individually which is terribly inefficient.
我还尝试通过简单地计算它们的相对位置来简单地将控件本身放入表单中。但是我觉得这是不好的编程习惯,因为它使得表单的布局以后很难改变。如果用户通过按旁边的“X”来删除行或缩放单位,此方法还需要程序找到该元素下方的每个元素并将其单独向上移动,这是非常低效的。
My question is: how would I go about creating a dynamically growing/shrinking application, either through Windows Forms layouts or WPF or something else?
我的问题是:我将如何通过 Windows 窗体布局或 WPF 或其他方式创建动态增长/缩小的应用程序?
回答by Usman Zafar
In WPF you can do this:
在 WPF 中,您可以这样做:
Classes
班级
public class MachineFunction
{
public string Name { get; set; }
public int Machines { get; set; }
public ObservableCollection<ScaleUnit> ScaleUnits { get; set; }
public MachineFunction()
{
ScaleUnits = new ObservableCollection<ScaleUnit>();
}
}
public class ScaleUnit
{
public string Name { get; set; }
public int Index { get; set; }
public ScaleUnit(int index)
{
this.Index = index;
}
}
Window.xaml
窗口.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ItemsControl Name="lstMachineFunctions">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="1" Text="Machine Function"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="Number of Machines"/>
<Button Grid.Row="1" Grid.Column="0" Click="OnDeleteMachineFunction">X</Button>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Name}"/>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Machines}"/>
</Grid>
<ItemsControl ItemsSource="{Binding ScaleUnits}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="12,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="1" Text="Machine/Scale Unit"/>
<Button Grid.Row="1" Grid.Column="0" Click="OnDeleteScaleUnit">X</Button>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Name}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Index, StringFormat='Scale Unit {0}'}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Button VerticalAlignment="Center" Click="OnAddScaleUnit">Add Scale Unit</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button HorizontalAlignment="Left" Click="OnAddMachineFunction">Add Machine Function</Button>
</StackPanel>
</Window>
Window.cs
窗口.cs
public partial class MainWindow : Window
{
public ObservableCollection<MachineFunction> MachineFunctions { get; set; }
public MainWindow()
{
InitializeComponent();
lstMachineFunctions.ItemsSource = MachineFunctions = new ObservableCollection<MachineFunction>();
}
private void OnDeleteMachineFunction(object sender, RoutedEventArgs e)
{
MachineFunctions.Remove((sender as FrameworkElement).DataContext as MachineFunction);
}
private void OnAddMachineFunction(object sender, RoutedEventArgs e)
{
MachineFunctions.Add(new MachineFunction());
}
private void OnAddScaleUnit(object sender, RoutedEventArgs e)
{
var mf = (sender as FrameworkElement).DataContext as MachineFunction;
mf.ScaleUnits.Add(new ScaleUnit(mf.ScaleUnits.Count));
}
private void OnDeleteScaleUnit(object sender, RoutedEventArgs e)
{
var delScaleUnit = (sender as FrameworkElement).DataContext as ScaleUnit;
var mf = MachineFunctions.FirstOrDefault(_ => _.ScaleUnits.Contains(delScaleUnit));
if( mf != null )
{
mf.ScaleUnits.Remove(delScaleUnit);
foreach (var scaleUnit in mf.ScaleUnits)
{
scaleUnit.Index = mf.ScaleUnits.IndexOf(scaleUnit);
}
}
}
}
回答by Nasreddine
I did the same thing recently in WinForms and the way I did it was as follows:
我最近在 WinForms 中做了同样的事情,我这样做的方式如下:
Create a UserControlthat contains the controls I wanted to repeat
创建一个UserControl包含我想重复的控件


Add a FlowLayoutPanelto the main form to contain all the user controls (and to simplify their positioning)
FlowLayoutPanel向主窗体添加一个以包含所有用户控件(并简化它们的定位)


Add a new instance of your custom UserControlto the FlowLayoutPanelevery time you want a new "row" of controls
每次您想要一个新的“行”控件时,将您的自定义的新实例添加UserControl到FlowLayoutPanel
flowLayoutPanel1.Controls.Add(
new MachineFunctionUC {
Parent = flowLayoutPanel1
});
To remove a row of control call this.Dispose();from within the user control (that's the instruction executed by the "X" button).
this.Dispose();从用户控件中删除一行控件调用(即“X”按钮执行的指令)。


If you want the UserControls to be arranged vertically set the following properties:
如果您希望用户控件垂直排列,请设置以下属性:
flowLayoutPanel1.AutoScroll = true;
flowLayoutPanel1.WrapContents = false;
flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
And to access them use flowLayoutPanel1.Controls[..]
并访问它们使用 flowLayoutPanel1.Controls[..]
回答by Sheridan
The correct way to achieve your requirements in WPF is for you to define a custom data type class to represent your machine function. Provide it with how ever many properties that you need to represent your machine fields. When you have done this, you then need to move the code that generated your machine function UI into a DataTemplatefor the type of your class and data bind all of the relevant properties:
在 WPF 中实现您的要求的正确方法是定义一个自定义数据类型类来表示您的机器功能。为其提供表示机器字段所需的属性数量。完成此操作后,您需要将生成机器功能 UI 的代码移动到DataTemplate用于类和数据绑定所有相关属性的类型中:
<DataTemplate DataType="{Binding YourPrefix:MachineFunction}">
...
</DataTemplate>
Then, you need to create a collection property to hold your machine function items and data bind that to some kind of collection control. Once you have done this, then to add another row, you just need to add another item to the collection:
然后,您需要创建一个集合属性来保存您的机器功能项,并将其数据绑定到某种集合控件。完成此操作后,要添加另一行,您只需将另一项添加到集合中:
<ItemsControl ItemsSource="{Binding MachineFunctions}">
<ItemsControl.Resources>
<DataTemplate DataType="{Binding YourPrefix:MachineFunction}">
...
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
<Button Content="+ Add Machine Function" ... />
...
...
MachineFunctions.Add(new MachineFunction());
Please see the Data Binding Overviewpage on MSDN for further help with data binding.
回答by Muhammad Usman
Create a function which will define a row for you. Consider the code and use its where to place another control and do as for buttons also and count it position.
创建一个函数,它将为您定义一行。考虑代码并使用它放置另一个控件的位置,并像按钮一样计算它的位置。
Button button1=new Button();
button1.Text="dynamic button";
button1.Left=10; button1.Top=10; //the button's location
this.Controls.Add(button1); //this is how you can add control

