WPF中动态添加控件

wfg2004 2008-05-11 04:24:35
前提: 在VS2008中用WPF(XAML)开打Window窗体

内容: 如何实现动态添加控件,比如动态添加3列,第一列是ComboBox,第二列是ComboBox,第三列是图片Button(这列你们可以不用关心,我用的是模板)
我的思路是在ListView中实现,代码如下
XAML的代码:
<ListView Grid.Row="3" Margin="0,5,0,0" Name="lvSelEdit" ItemContainerStyle="{StaticResource ListViewItemControl}">
<ListView.View>
<GridView>
<GridViewColumn Header="EType" Width="200"/>
<GridViewColumn Header="Exam" Width="200"/>
<GridViewColumn CellTemplate="{StaticResource DeleteImgCell}" Header="Delete" Width="100"/>
</GridView>
</ListView.View>
</ListView>
后台C#的代码:
private void btnNew_Click(object sender, RoutedEventArgs e)
{
Object []obj = new object[2];
ComboBox ExamType = new ComboBox();
ExamType.Items.Insert(0, "Test0");
ExamType.Items.Insert(1, "Test1");
ExamType.Items.Insert(2, "Test2");
ComboBox Exam = new ComboBox();
Exam.Items.Insert(0, "Test3");
Exam.Items.Insert(1, "Test4");
Exam.Items.Insert(2, "Test5");
obj[0] = ExamType;
obj[1] = Exam;
alist.Add(obj);
lvSelEdit.ItemsSource = null;
lvSelEdit.ItemsSource = alist;
}
当我这么填充的时候,显示在页面中的不是ComboBox控件,而是文字(array[0]和array[1])
怎么才能把这个数组按控件的形式显示阿?
哪位高手会阿?

或者别的方法也行啊....
总之是动态添加的方法///

分数不够我可以再添加的。

...全文
3314 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
YU494092072 2012-07-11
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 的回复:]
XML code
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local……
[/Quote]
请问如何获取 <DataTemplate>下面的<ComboBox Name="cbx"/>???
324374 2011-04-03
  • 打赏
  • 举报
回复
我的回复哪去了?晕,不小心删了?那只能再说一遍:上面代码是我自己写的,你代码的DeleteImgCell无从得知,不显示数据原因就是动态添加的数据和DataTemplate的Binding不一致。如上程序,模拟你的要求。一个ListView,窗体加载后的Loaded事件会自动添加数据,ListView会显示人的姓名和性别,性别用ComboBox显示。。。
324374 2011-04-03
  • 打赏
  • 举报
回复

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300" Loaded="Window_Loaded">
<Grid>
<ListView Name="listView">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="姓名" Width="100" DisplayMemberBinding="{Binding Name}"></GridViewColumn>
<GridViewColumn Header="性别" Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="cbx" SelectedIndex="0">
<ComboBoxItem>男</ComboBoxItem>
<ComboBoxItem>女</ComboBoxItem>
</ComboBox>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Man}" Value="false">
<Setter TargetName="cbx" Property="SelectedIndex" Value="1"></Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>


    public partial class Window1 : Window
{
Random r = new Random();
public Window1()
{
InitializeComponent();
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
listView.Items.Add(new Person() { Name = "Mgen", Man = true });
listView.Items.Add(new Person() { Name = "234235", Man = false });
}
}

public class Person
{
public string Name { get; set; }
public bool Man { get; set; }
}
gg321654987 2011-04-03
  • 打赏
  • 举报
回复
长期开发ASP.NET,会认为修改列表型控件的项属性是很简单的事情。

但是到了WPF中,似乎不那么容易。

以ListView控件为例,如果我们通过ListView.Items获取集合并用索引获取单项时,我们会发现我们所获得的对象类型是ItemSource绑定的数据集合的单项数据类型。也就是说,ListView.Items其实就是数据集合,因此项也是数据集合的项,这样的话,我们就不可能对单项的样式进行设置,因为他是数据而不是我们要的控件对象。



通过在MSDN上查看ListView的成员列表。很容易发现要获取显示出来的列表项控件集合实际上是通过ItemContainerGenerator这个属性。从名字上很容易看出来,项容器生成器。这样就可以得到一个ListView生成成列表后的容器控件集合。然后通过 ContainerFromIndex方法和ContainerFromItem就可以获得单项的项控件。

问题似乎解决了,定义一个ListViewItem来接收它,然后可以方便的设置属性了。

ListViewItem lvi = (ListViewItem)grid_OperationRecordsList.ItemContainerGenerator.ContainerFromIndex(0);

lvi.FontWeight = FontWeights.Bold;

但是这个时候你会发现,这两句代码放在控件的Loaded事件里或窗体或Page的Loaded事件里,都会报错,ListViewItem为空。检查数据,发现有数据,我的示例中,索引号是0,因此只要有数据,应该都是生效的。



为什么呢?



经过思考和测试发现,如果另设置一个按钮,并将这两句放在这个按钮的Click事件里就没有问题。

于是得出结论,虽然Loaded事件是最后执行的事件,但是此事件依然是在UI绘制之前就完成了的,因此我们无法获得UI绘制好以后才被实例化的ListViewItem。

换句话说,窗体Loaded事件是在界面呈现出来前的最后一个事件,而ListViewItem是窗体呈现出来后才产生的,所以无论怎么写,在任何控件的Loaded事件中ListViewItem都是NULL。



在网上找到一个例子,采用的方法是将处理ListViewItem的方法调度到UI线程之后,但是失败了,因为,似乎无法找到任何事件可以执行这个调度,因为任何事件都不是这个UI线程的末尾。



此时,又找到一个新事件,ListView的ItemContainerGenerator属性具备一个事件StatusChanged,想必此事件就是在绘制界面上ListView每一项的状态事件。因此将处理ListViewItem的代码写在这个事件上。

在窗体的Loaded事件中为ListView.ItemContainerGenerator.StatusChanged 事件绑定处理函数。

ListView.ItemContainerGenerator.StatusChanged += new EventHandler(ItemContainerGenerator_StatusChanged);

然后在这个函数里,写入处理代码

void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{

ListViewItem lvi = (ListViewItem)grid_OperationRecordsList.ItemContainerGenerator.ContainerFromIndex(0);

if (lvi != null)
{
lvi.FontWeight = FontWeights.Bold;
}
}



看上去应该成功了,但是运行后,报错:调度程序进程挂起时无法执行此操作



也就是说,这个事件触发的时候,程序主进程已经挂起了?挂起了,当然无法再进行任何修改的操作。

不过,既然到了这一步,那么变通方法就很多了。最直接的,采用多线程吧。



在StatusChanged 事件里,再创建一个线程,然后将这个线程调度到UI线程上,修改ListViewItem的属性。



成功!

代码如下

delegate void VoidDelegate();

void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{

System.Threading.ThreadStart ts = new System.Threading.ThreadStart(JugeRead);
System.Threading.Thread t = new System.Threading.Thread(ts);
t.Start();

}

private void JugeRead()
{
this.Dispatcher.Invoke(new VoidDelegate(MarkRead),null);
}

private void MarkRead()
{
ListViewItem lvi = (ListViewItem)grid_OperationRecordsList.ItemContainerGenerator.ContainerFromIndex(0);

if (lvi != null)
{
lvi.FontWeight = FontWeights.Bold;
}
}




完整代码如下:

private void Page_Loaded(object sender, RoutedEventArgs e)
{
ListView.ItemContainerGenerator.StatusChanged += new EventHandler(ItemContainerGenerator_StatusChanged);
}



delegate void VoidDelegate();

void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{

System.Threading.ThreadStart ts = new System.Threading.ThreadStart(JugeRead);
System.Threading.Thread t = new System.Threading.Thread(ts);
t.Start();

}

private void JugeRead()
{
this.Dispatcher.Invoke(new VoidDelegate(MarkRead),null);
}

private void MarkRead()
{
ListViewItem lvi = (ListViewItem)grid_OperationRecordsList.ItemContainerGenerator.ContainerFromIndex(0);

if (lvi != null)
{
lvi.FontWeight = FontWeights.Bold;
}
}



整个过程不同于以往的理解。

大概可以这样描述。

ListView.ItemContainerGenerator是界面绘制完成以后才被实例的对象。

因此无法在界面呈现出来的任何事件里获得ItemContainerGenerator对象,需要等待界面完成绘制。

在绘制过程中,会触发ItemContainerGenerator的StatusChanged事件。于是可以通过StatusChanged事件来处理ListViewItem.

但是在界面完成绘制后,进程被挂起。于是,我们需要激活进程,让他重新执行我们的操作。

所以,在StatusChanged事件中,我们需要创建一个新的线程,然后通过调度该线程,让主线程执行我们的ListViewItem操作的代码。



我认为,应该还有更简便的方法可以实现,不过暂时还没发现其他在我的项目中有效的方法。当然,也可以通过Timer来实现,但是个人觉得用Timer逻辑上并不是很好的解决方案。



===================后续==================

经过调整,我发现,其实不仅在StatusChanged创建线程可以实现,既然创建了新线程,并且将该线程调度到了UI线程,那么其实在任何事件上都可以创建这个线程并运行成功。

代码如下:

delegate void VoidDelegate();

void CheckRead()
{
System.Threading.ThreadStart ts = new System.Threading.ThreadStart(JugeRead);
System.Threading.Thread t = new System.Threading.Thread(ts);
t.Start();
}

private void JugeRead()
{
this.Dispatcher.Invoke(new VoidDelegate(MarkRead),null);
}

private void MarkRead()
{
ListViewItem lvi = (ListViewItem)grid_OperationRecordsList.ItemContainerGenerator.ContainerFromIndex(0);

if (lvi != null)
{
lvi.FontWeight = FontWeights.Bold;
}
}



封装一个创建线程并执行方法的方法。然后将该方法调用在任何事件中都可以实现你想要的效果!

这个里面就是通过后台代码实现动态添加控件的方法,因为可以帮助你完成。

http://blog.sina.com.cn/s/blog_685790700100ltmh.html
bryht 2010-10-19
  • 打赏
  • 举报
回复
Exam.Items.Add()有这个吗?
whitechololate 2010-10-19
  • 打赏
  • 举报
回复
Telerik.Windows.Controls.GridViewDataColumn gvc = new Telerik.Windows.Controls.GridViewDataColumn();
DataTemplate nameTemplate = new DataTemplate();
FrameworkElementFactory nameFactory = new FrameworkElementFactory(typeof(RadComboBox));
Binding binding = new Binding();
ArrayList al = new ArrayList();//把数据源中显示内容提取出来放到al中
foreach (CiTiao ct in efp.GetCitiaoListByOptionID(_zYXTabList[i].XuanXiangID))
{
al.Add(ct.CiTiaoName);
}
binding.Source = al;
nameFactory.SetBinding(RadComboBox.ItemsSourceProperty, binding);


nameTemplate.VisualTree = nameFactory;
nameTemplate.Seal();
gvc.CellEditTemplate = nameTemplate;

gvc.Header = _zYXTabList[i].Name;

//gvc.DataMemberBinding = bind;
gvc.Width = 80;
TabGrid.Columns.Add(gvc);
现在正在研究,这是加一个模板编辑列,实现到这步了,就是下拉框里面的值选中后,不能留在Gridview的单元格中,正郁闷呢
weng3998 2009-08-10
  • 打赏
  • 举报
回复
Children.Add() 就行了 和 controls。add() 一样
weng3998 2009-08-10
  • 打赏
  • 举报
回复
我也在找 在 winform 里 controls。add() 就出来了 WPF 怎么办 总不能定义一段 axml 再插进去吧 也太傻了 。
zhouyongh 2008-06-12
  • 打赏
  • 举报
回复
还在询问么?
wfg2004 2008-05-19
  • 打赏
  • 举报
回复
我代码中有,是因为那列是用的静态数据源,我想用动态数据源,所以比较困惑。
wxg22526451 2008-05-11
  • 打赏
  • 举报
回复
关注
曲滨_銘龘鶽 2008-05-11
  • 打赏
  • 举报
回复
下列框也和按钮一样的
你代码中不是有吗?
<GridViewColumn CellTemplate="{StaticResource DeleteImgCell}" Header="Delete" Width="100"/>

你cs 代码中把 ComboBox 当作数据传递进去那那里行;

直接做一个里面带 comboBox 的 CellTemplate 然后给 comboBox 赋值就可以了啊

110,534

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧