转载

WPF快速实现XML可视化编辑工具

虽然最近业余时间主要都放在研究AngularJS上了,不过由于正好要帮朋友做一个生成XML的小工具,顺便又温顾了一下WPF。虽然这个时代相对于Web应用和移动App,Windows应用程序是越来越少了,但是微软并未因此放弃它,反而推出了强大的WPF,让Windows应用程序的制作变得更优雅、更高效。

在我看来,WPF最大的强项就是布局和绑定了。WPF引入了MVVM的编程模式,并结合页面绑定,让UI和业务逻辑完全可以分离由不同的人去完成,而且只要View-Model保持稳定,对于View的布局变动将不受任何限制。因此WPF的编程思维和Winform已经完全不一样,如果你是一个从来没用过WPF的Winform程序员,你首要要做的应该是改变你的思维模式。

案例需求

需要一个工具,按照某机构的官方文档要求,数据由用户通过程序界面输入,最终生成指定格式的XML文件(某机构已提供XSLT文件,因此生成的XML可以在浏览器中展示出统一的格式。关于XSLT并不在本篇讨论范围内,以后有机会可以另外开篇再说)。

Winform的思路

  1. 拖控件布局
  2. 创建控件的各种事件
  3. 通过后台代码控制界面布局以及元素行为
  4. 后台代码获取元素的值并将他们赋值给所需的对象
  5. 写非常复杂的if-else逻辑生成所需的XML
  6. 如果需要将生成的XML重新绑定到页面上,又是写一遍非常复杂的逻辑进行页面控件赋值

WPF的思维

  1. 将XML抽象成实体类
  2. 创建实例并将它设置为View的DataContext
  3. 将实例的各个属性绑定到View的各个控件中
  4. 按需在界面上填写数据后,将实例序列化成XML
  5. 如果需要将生成的XML重新绑定到页面上,将文件内容反序列化为实例对象,绑定到DataContext即可

光看文字描述,对于不熟悉WPF的人来说可能很难分清他们的区别,我们看下具体代码吧。为了简化业务,我重新写了一个Demo,通过页面上输入班级、老师、学生信息,生成一个包含班级信息的XML文件。

步骤1:抽象XML实体对象

为了能让实体对象的实例最终和View进行自动的双向绑定,我们需要将所有实体类实现INotifyPropertyChanged接口,为了进一步抽象代码,我们首先创建一个实现了INotifyPropertyChanged的基类,所有实体类将继承该基类。

1     public abstract class ClassBase : INotifyPropertyChanged 2     { 3         public event PropertyChangedEventHandler PropertyChanged; 4         protected void NotifyPropertyChange(string propertyName) 5         { 6             if (PropertyChanged != null) 7                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 8         } 9     }

班级类(老师、学生属性使用ObservableCollection集合,可以使集合变动时界面也自动刷新布局):

 1     [XmlRoot(ElementName = "class")]  2     public class MyClass : ClassBase  3     {  4         private string _grade;  5         [XmlAttribute(AttributeName = "grade", Namespace = "")]  6         public string Grade  7         {  8             get  9             { 10                 return _grade; 11             } 12             set 13             { 14                 _grade = value; 15                 NotifyPropertyChange("Grade"); 16             } 17         } 18  19         private string _classID; 20         [XmlAttribute(AttributeName = "class-id", Namespace = "")] 21         public string ClassID 22         { 23             get 24             { 25                 return _classID; 26             } 27             set 28             { 29                 _classID = value; 30                 NotifyPropertyChange("ClassID"); 31             } 32         } 33  34         private ObservableCollection<MyTeacher> _teachers; 35         [XmlElement(ElementName = "teachers", Namespace = "")] 36         public ObservableCollection<MyTeacher> Teachers 37         { 38             get 39             { 40                 return _teachers; 41             } 42             set 43             { 44                 _teachers = value; 45                 NotifyPropertyChange("Teachers"); 46             } 47         } 48  49         private ObservableCollection<MyStudent> _students; 50         [XmlElement(ElementName = "students", Namespace = "")] 51         public ObservableCollection<MyStudent> Students 52         { 53             get 54             { 55                 return _students; 56             } 57             set 58             { 59                 _students = value; 60                 NotifyPropertyChange("Students"); 61             } 62         } 63     }

老师类:

 1     [XmlRoot(ElementName = "teacher")]  2     public class MyTeacher : ClassBase  3     {  4         private string _name;  5         [XmlElement(ElementName = "name", Namespace = "")]  6         public string Name  7         {  8             get  9             { 10                 return _name; 11             } 12             set 13             { 14                 _name = value; 15                 NotifyPropertyChange("Name"); 16             } 17         } 18  19         private string _teachingFor; 20         [XmlElement(ElementName = "teaching-for", Namespace = "")] 21         public string TeachingFor 22         { 23             get 24             { 25                 return _teachingFor; 26             } 27             set 28             { 29                 _teachingFor = value; 30                 NotifyPropertyChange("TeachingFor"); 31             } 32         } 33  34         private string _comments; 35         [XmlElement(ElementName = "comments", Namespace = "")] 36         public string Comments 37         { 38             get 39             { 40                 return _comments; 41             } 42             set 43             { 44                 _comments = value; 45                 NotifyPropertyChange("Comments"); 46             } 47         } 48     }

学生类:

 1     [XmlRoot(ElementName = "student")]  2     public class MyStudent : ClassBase  3     {  4         private string _name;  5         [XmlElement(ElementName = "name", Namespace = "")]  6         public string Name  7         {  8             get  9             { 10                 return _name; 11             } 12             set 13             { 14                 _name = value; 15                 NotifyPropertyChange("Name"); 16             } 17         } 18  19         private int _age; 20         [XmlElement(ElementName = "age", Namespace = "")] 21         public int Age 22         { 23             get 24             { 25                 return _age; 26             } 27             set 28             { 29                 _age = value; 30                 NotifyPropertyChange("Age"); 31             } 32         } 33  34         private string _gender; 35         [XmlElement(ElementName = "gender", Namespace = "")] 36         public string Gender 37         { 38             get 39             { 40                 return _gender; 41             } 42             set 43             { 44                 _gender = value; 45                 NotifyPropertyChange("Gender"); 46             } 47         } 48     }

OK,至此为止,我们Demo所需的XML实体类已抽象完毕。

步骤2:创建实例并将它设置为View的DataContext

 1     public partial class MainWindow : Window  2     {  3         // 创建空实例  4         private MyClass _myClassInfo = new MyClass();  5   6         public MainWindow()  7         {  8             InitializeComponent();  9  10             //将空实例设置为View的DataContext 11             base.DataContext = _myClassInfo; 12         } 13     }

对,你没看错,这一步就是如此简单!其实就注释的那2行代码而已!

步骤3:将实例的各个属性绑定到View的各个空间中

班级信息界面代码:

 1         <TextBlock Grid.Row="0" Grid.Column="0" Text="Grade:"></TextBlock>  2         <ComboBox Grid.Row="0" Grid.Column="1" Text="{Binding Path=Grade}">  3             <ComboBoxItem Content="Grade 1"></ComboBoxItem>  4             <ComboBoxItem Content="Grade 2"></ComboBoxItem>  5             <ComboBoxItem Content="Grade 3"></ComboBoxItem>  6             <ComboBoxItem Content="Grade 4"></ComboBoxItem>  7             <ComboBoxItem Content="Grade 5"></ComboBoxItem>  8         </ComboBox>  9  10         <TextBlock Grid.Row="1" Grid.Column="0" Text="ClassID:"></TextBlock> 11         <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=ClassID}"></TextBox>

老师信息界面代码:

 1     <GroupBox Header="Teachers" Grid.Row="2" Grid.ColumnSpan="2">  2             <ContentControl>  3                 <Grid>  4                     <Grid.RowDefinitions>  5                         <RowDefinition Height="*"></RowDefinition>  6                         <RowDefinition Height="30"></RowDefinition>  7                     </Grid.RowDefinitions>  8                       9                     <TabControl x:Name="tabTeachers" ItemsSource="{Binding Path=Teachers}"> 10                         <TabControl.ItemTemplate> 11                             <DataTemplate> 12                                 <TextBlock Text="{Binding Path=Name, Converter={StaticResource TeacherNameConverter}}" MinWidth="30"></TextBlock> 13                             </DataTemplate> 14                         </TabControl.ItemTemplate> 15                         <TabControl.ContentTemplate> 16                             <DataTemplate> 17                                 <Grid> 18                                     <Grid.ColumnDefinitions> 19                                         <ColumnDefinition Width="160"></ColumnDefinition> 20                                         <ColumnDefinition Width="*"></ColumnDefinition> 21                                     </Grid.ColumnDefinitions> 22  23                                     <Grid.RowDefinitions> 24                                         <RowDefinition Height="30"></RowDefinition> 25                                         <RowDefinition Height="30"></RowDefinition> 26                                         <RowDefinition Height="30"></RowDefinition> 27                                     </Grid.RowDefinitions> 28  29                                     <Label Grid.Row="0" Grid.Column="0" Content="Teacher name"></Label> 30                                     <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Path=Name}"></TextBox> 31  32                                     <Label Grid.Row="1" Grid.Column="0" Content="Teaching for"></Label> 33                                     <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=TeachingFor}"></TextBox> 34  35                                     <Label Grid.Row="2" Grid.Column="0" Content="Comments"></Label> 36                                     <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Path=Comments}"></TextBox> 37                                 </Grid> 38                             </DataTemplate> 39                         </TabControl.ContentTemplate> 40                     </TabControl> 41                      42                     <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center"> 43                         <Button Name="btnNewTeacher" Content="Create New Teacher" Width="150" Margin="0,0,20,0" Click="btnNewTeacher_Click"></Button> 44                         <Button Name="btnDeleteTeacher" Content="Delete Current Teacher" Width="150" Click="btnDeleteTeacher_Click"></Button> 45                     </StackPanel> 46                 </Grid> 47             </ContentControl> 48         </GroupBox>

学生信息界面代码:

 1     <GroupBox Header="Students" Grid.Row="3" Grid.ColumnSpan="2">  2             <ContentControl>  3                 <Grid>  4                     <Grid.RowDefinitions>  5                         <RowDefinition Height="*"></RowDefinition>  6                         <RowDefinition Height="30"></RowDefinition>  7                     </Grid.RowDefinitions>  8   9                     <TabControl x:Name="tabStudents" ItemsSource="{Binding Path=Students}"> 10                         <TabControl.ItemTemplate> 11                             <DataTemplate> 12                                 <TextBlock Text="{Binding Path=Name, Converter={StaticResource StudentNameConverter}}" MinWidth="30"></TextBlock> 13                             </DataTemplate> 14                         </TabControl.ItemTemplate> 15                         <TabControl.ContentTemplate> 16                             <DataTemplate> 17                                 <Grid> 18                                     <Grid.ColumnDefinitions> 19                                         <ColumnDefinition Width="160"></ColumnDefinition> 20                                         <ColumnDefinition Width="*"></ColumnDefinition> 21                                     </Grid.ColumnDefinitions> 22  23                                     <Grid.RowDefinitions> 24                                         <RowDefinition Height="30"></RowDefinition> 25                                         <RowDefinition Height="30"></RowDefinition> 26                                         <RowDefinition Height="30"></RowDefinition> 27                                     </Grid.RowDefinitions> 28  29                                     <Label Grid.Row="0" Grid.Column="0" Content="Student name"></Label> 30                                     <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Path=Name}"></TextBox> 31  32                                     <Label Grid.Row="1" Grid.Column="0" Content="Age"></Label> 33                                     <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=Age}"></TextBox> 34  35                                     <Label Grid.Row="2" Grid.Column="0" Content="Gender"></Label> 36                                     <ComboBox Grid.Row="2" Grid.Column="1" Text="{Binding Path=Gender}"> 37                                         <ComboBoxItem Content="Male"></ComboBoxItem> 38                                         <ComboBoxItem Content="Female"></ComboBoxItem> 39                                     </ComboBox> 40                                 </Grid> 41                             </DataTemplate> 42                         </TabControl.ContentTemplate> 43                     </TabControl> 44  45                     <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center"> 46                         <Button Name="btnNewStudent" Content="Create New Student" Width="150" Margin="0,0,20,0" Click="btnNewStudent_Click"></Button> 47                         <Button Name="btnDeleteStudent" Content="Delete Current Student" Width="150" Click="btnDeleteStudent_Click"></Button> 48                     </StackPanel> 49                 </Grid> 50             </ContentControl> 51         </GroupBox>

步骤4:按需在界面上填写数据后,将实例序列化成XML

 1 string xmlFilePath = txtFilePath.Text.Trim();  2 if (!string.IsNullOrEmpty(xmlFilePath))  3 {  4     XmlWriterSettings settings = new XmlWriterSettings()  5     {  6         Encoding = Encoding.UTF8,  7         OmitXmlDeclaration = true,  8         NewLineOnAttributes = true,  9         Indent = true, 10         ConformanceLevel = ConformanceLevel.Document 11     }; 12  13     XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); 14     ns.Add("", ""); 15  16     using (FileStream fs = new FileStream(xmlFilePath, FileMode.Create)) 17     using (var writer = XmlWriter.Create(fs, settings)) 18     { 19         writer.WriteRaw("<?xml version=/"1.0/" encoding=/"UTF-8/"?>/r/n"); 20  21         XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyClass)); 22         xmlSerializer.Serialize(writer, _myClassInfo, ns); 23         System.Windows.Forms.MessageBox.Show("Success!"); 24     } 25 } 26 else 27 { 28     System.Windows.Forms.MessageBox.Show("Choose a file path to save!"); 29 }

步骤5:读取已有的XML绑定到页面上

 1 OpenFileDialog dialog = new OpenFileDialog();  2 dialog.DefaultExt = "xml";  3 dialog.Filter = "XML documents (*.xml)|*.xml";  4 dialog.FileName = "my-class-test";  5   6 var dr = dialog.ShowDialog();  7 if (dr == System.Windows.Forms.DialogResult.OK)  8 {  9     txtFilePath.Text = dialog.FileName; 10  11     using (FileStream fs = File.OpenRead(dialog.FileName)) 12     { 13         XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyClass)); 14         _myClassInfo = xmlSerializer.Deserialize(fs) as MyClass; 15         base.DataContext = _myClassInfo; 16  17         this.tabStudents.SelectedIndex = 0; 18         this.tabTeachers.SelectedIndex = 0; 19     } 20 }

好了,这样程序就已经完成了。你没看错,这已经是几乎所有代码了!是不是很不可思议?你可能已经有心理准备,WPF将会以非常优雅的代码完成我们所需的逻辑,但是这也太神奇了!区区百行代码竟然完成了Winform中可能需要数倍代码量的逻辑!想象中的后台组装MyClass实例并生成XML的代码竟然都已经由WPF的双向绑定方式悄悄帮你做完了!

看完这个示例,你是否也开始蠢蠢欲动,想自己动手试试写一个属于自己的WPF程序了呢?当然如果你已经等不及了,你也可以先下载附录中的源码运行一下,一睹为快。

附录

本文Demo完整源码下载地址(VS2012): http://files.cnblogs.com/files/wushangjue/WpfDemo.zip

正文到此结束
Loading...