在wpf开发中,虽然可以通过XMAL编写炫酷的界面,但是有时候需要动态定义控件即:前台界面控件数量或者类型需要解析的数据或者其它条件确认再生成,这时候我们就需要通过后台cs中编写代码实现这一功能。.
01—功能演示

02—功能说明
以上演示部分我们可以看到我前台的部分界面在窗体加载后并没有显示,而是选择文件解析后自动产生的,这种场景有时候也挺常用,特别是有大量同类型的数据显示到同类型的控件中时,我们就可以通过导入txt、Xml等文件的形式然后自动生成. 本地主要是举例演示实现这一功能,使用场景造得可能并不恰当,大家忍受下。
03—源码实现
前台代码:
<UserControl x:Class="Caliburn.Micro.Hello.DynamicalView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="clr-namespace:Caliburn.Micro.Hello"xmlns:cal="http://www.caliburnproject.org" xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"mc:Ignorable="d"d:DesignHeight="450" d:DesignWidth="800"><Grid><Grid.RowDefinitions><RowDefinition Height="1.5*" /><RowDefinition Height="8.5*" /></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions><StackPanel Orientation="Horizontal" VerticalAlignment="Center" Grid.Row="0" Grid.ColumnSpan="2"><TextBox Width="500" Height="30" Margin="3" Text="{Binding FilePath}" FontSize="14"FontStyle="Normal" IsReadOnly="True" /><Button Content="..." Margin="3" MinWidth="50"cal:Message.Attach="[Event Click] = [Action SelectFile()]" /></StackPanel><GroupBox Grid.Column="0" Grid.Row="1" Margin="3"><GroupBox.Header><dxlc:LayoutItem Label="Student" Foreground ="Green" /></GroupBox.Header><dxlc:LayoutControl><Grid HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="0"cal:Message.Attach="[Event Loaded] = [Action StudentGridLoaded($source)]" /></dxlc:LayoutControl></GroupBox><GroupBox Grid.Column="1" Grid.Row="1" Margin="3"><GroupBox.Header><dxlc:LayoutItem Label="Teacher" Foreground ="Blue" /></GroupBox.Header><dxlc:LayoutControl><Grid HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="0"cal:Message.Attach="[Event Loaded] = [Action TeacherGridLoaded($source)]" /></dxlc:LayoutControl></GroupBox></Grid></UserControl>
这里使用了Caliburn.Micro框架,所以需要引用名称空间
xmlns:cal="http://www.caliburnproject.org"因为控件数量不确定,需要显示不全时行列可以拖动,实现这一功能只需要把控件包裹进:<dxlc:LayoutControl>就可以。
后台代码:
using DevExpress.Xpf.Editors;using PropertyChanged;using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Threading;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Forms;using Binding = System.Windows.Data.Binding;using HorizontalAlignment = System.Windows.HorizontalAlignment;using Label = System.Windows.Controls.Label;namespace Caliburn.Micro.Hello{[AddINotifyPropertyChangedInterface]public class DynamicalViewModel : Screen, IViewModel{private readonly AutoResetEvent StudentGridLoad = new AutoResetEvent(false);private readonly AutoResetEvent TeacherGridLoad = new AutoResetEvent(false);public string FilePath { get; set; } = @"D:\test.txt";public List<PersonInfoDTO> PersonInfoList = new List<PersonInfoDTO>();public PersonInfo PersonInfo { get; set; }public DynamicalViewModel(){DisplayName = "DynamicalControls";}public void DispalyReuslt(){Task.Run(() =>{ParseData();Execute.OnUIThread(() =>{AddGridControl();});});}private void ParseData(){var lines = File.ReadAllLines(FilePath);foreach (string line in lines){var strs = line.Split(':');if (strs.Count() > 1){var infos = strs[1].Split(',');PersonInfoList.Add(new PersonInfoDTO(){InfoType = strs[0],PersonInfo = new PersonInfo(){Name = infos[0],Sex = infos[1],Age = Convert.ToInt32(infos[2])}});}}}public void SelectFile(){string defaultInputFolder = @"D:\test.txt";OpenFileDialog fileDialog = new OpenFileDialog();if (defaultInputFolder != null){fileDialog.InitialDirectory = defaultInputFolder;}fileDialog.Multiselect = false;//该值确定是否可以选择多个文件fileDialog.Title = "请选择ReportFile文件";fileDialog.Filter = "文本文件(*.txt)|*.txt";if (fileDialog.ShowDialog() == DialogResult.OK){FilePath = fileDialog.FileName;}DispalyReuslt();}private Grid StudentGrid { get; set; }private Grid TeacherGrid { get; set; }public void StudentGridLoaded(object sender){StudentGrid = (Grid)sender;}public void TeacherGridLoaded(object sender){TeacherGrid = (Grid)sender;}int studentRowIndex = 0;int studentColumnIndex = 0;int teacherRowIndex = 0;int teacherColumnIndex = 0;private void AddGridControl(){int StudentConut = 0;int TeacherCount = 0;foreach (var item in PersonInfoList){if (item.InfoType == "老师"){TeacherCount++;}else{StudentConut++;}}StudentGrid.Children.Clear();StudentGrid.ColumnDefinitions.Clear();StudentGrid.RowDefinitions.Clear();TeacherGrid.Children.Clear();TeacherGrid.ColumnDefinitions.Clear();TeacherGrid.RowDefinitions.Clear();var gridColumns = 4;var successGridRows = Math.Ceiling(StudentConut / 2.0);var failGridRows = Math.Ceiling(TeacherCount / 2.0);//添加grid列for (int i = 0; i < gridColumns; i++){var successColumnDefinition = new ColumnDefinition();StudentGrid.ColumnDefinitions.Add(successColumnDefinition);var failedColumnDefinition = new ColumnDefinition();TeacherGrid.ColumnDefinitions.Add(failedColumnDefinition);}//添加grid行for (int i = 0; i < successGridRows; i++){var successRowDefinition = new RowDefinition();StudentGrid.RowDefinitions.Add(successRowDefinition);successRowDefinition.Height = new GridLength(30, GridUnitType.Pixel);//绝对尺寸}for (int i = 0; i <= failGridRows; i++){var failedRowDefinition = new RowDefinition();TeacherGrid.RowDefinitions.Add(failedRowDefinition);failedRowDefinition.Height = new GridLength(30, GridUnitType.Pixel);//绝对尺寸}int rowIndex = 0;int columnIndex = 0;UIElement uIElement = new UIElement();foreach (var item in PersonInfoList){if (item.InfoType == "学生"){if (studentColumnIndex / 4 == 1){studentColumnIndex = 0;studentRowIndex++;}rowIndex = studentRowIndex;columnIndex = studentColumnIndex;}else{if (teacherColumnIndex / 4 == 1){teacherColumnIndex = 0;teacherRowIndex++;}rowIndex = teacherRowIndex;columnIndex = teacherColumnIndex;}if (columnIndex % 2 == 0){Label label = new Label();label.HorizontalAlignment = HorizontalAlignment.Right;label.VerticalAlignment = VerticalAlignment.Center;label.Width = 100;label.Content = item.PersonInfo.Name;label.SetValue(Grid.RowProperty, rowIndex);label.SetValue(Grid.ColumnProperty, columnIndex);uIElement = label;if (item.InfoType == "学生"){StudentGrid.Children.Add(uIElement);studentColumnIndex++;columnIndex = studentColumnIndex;}else{TeacherGrid.Children.Add(uIElement);teacherColumnIndex++;columnIndex = teacherColumnIndex;}}TextEdit textBox = new TextEdit();textBox.HorizontalAlignment = HorizontalAlignment.Left;textBox.VerticalAlignment = VerticalAlignment.Center;textBox.Name = item.PersonInfo.Name;textBox.Width = 100;textBox.Height = 25;textBox.SetValue(Grid.RowProperty, rowIndex);textBox.SetValue(Grid.ColumnProperty, columnIndex);var path = item.PersonInfo.GetType().GetProperty("Age");Binding binding = new Binding(){Source = item.PersonInfo,Path = new PropertyPath(path),Mode = BindingMode.TwoWay,UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged};textBox.SetBinding(TextEdit.TextProperty, binding);uIElement = textBox;if (item.InfoType == "学生"){StudentGrid.Children.Add(uIElement);studentColumnIndex++;columnIndex = studentColumnIndex;}else{TeacherGrid.Children.Add(uIElement);teacherColumnIndex++;columnIndex = teacherColumnIndex;}}}}}
数据模型:
public class PersonInfo{public string Name { get; set; }public int Age { get; set; }public string Sex { get; set; }public override string ToString(){string report = $"[Name] = [{Name}],[Age] = [{Age}],[Sex] = [{Sex}]";return report;}}public class PersonInfoEven : PersonInfo{}public class PersonInfoDTO{public string InfoType { get; set; }public PersonInfo PersonInfo { get; set; }}
这里需要注意一些地方:
①首先StudentGridLoaded和TeacherGridLoaded是在viewmodel初始化完成后才加载的,所以在构造函数执行完后还是null;
②加载控件和解析数据比较慢我放在了线程Task.Run运行,但是线程中更新界面又需要用委托实现,这里CM给我们封装了方法
Execute.OnUIThread(() => { });③:grid行列添加:
var successColumnDefinition = new ColumnDefinition();StudentGrid.ColumnDefinitions.Add(successColumnDefinition);var successRowDefinition = new RowDefinition();StudentGrid.RowDefinitions.Add(successRowDefinition);
④通过代码生成TextEdit,bing数据并添加到grid中:
TextEdit textBox = new TextEdit();textBox.HorizontalAlignment = HorizontalAlignment.Left;textBox.VerticalAlignment = VerticalAlignment.Center;textBox.Name = item.PersonInfo.Name;textBox.Width = 100;textBox.Height = 25;textBox.SetValue(Grid.RowProperty, rowIndex);textBox.SetValue(Grid.ColumnProperty, columnIndex);var path = item.PersonInfo.GetType().GetProperty("Age");Binding binding = new Binding(){Source = item.PersonInfo,Path = new PropertyPath(path),Mode = BindingMode.TwoWay,UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged};textBox.SetBinding(TextEdit.TextProperty, binding);uIElement = textBox;TeacherGrid.Children.Add(uIElement);
⑤遍历grid中的控件:
foreach (UIElement uiElement in failedParsedGrid.Children){if (uiElement is TextEdit){TextEdit textBox = uiElement as TextEdit;switch (textBox.Name){//todo}}}
⑥通过反射遍历属性:
foreach (PropertyInfo info in PersonInfo.GetType().GetProperties()){var itemValue = info.GetValue(PersonInfo);// TO DO}