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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM