在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 != ) { 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初始化完成後才加載的,是以在構造函數執行完後還是;
②加載控件和解析資料比較慢我放在了線程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 }