WPF學習之Binding(二)


前言:

(一)里面簡單學習了Binding的基礎、源、路徑、如何控制Binding的方向及數據更新、使用DataContext作為Binding的源等

使用集合對象作為列表控件的ItemSource

WPF列表式控件派生自ItemControl類,自然繼承了ItemSource這個屬性。

例如:ListBox條目容器ListBoxItem,ComboBox條目容器ComboBoxItem。

ItemSource里存着一條條數據,想要顯示出來必須借助條目容器。Binging就是用來聯系條目容器和數據的。

只要我們為一個ItemsControl設置一個ItemsSource屬性值,ItemsControl對象就會自動迭代其中的數據元素,為每個數據元素准備一條條目容器,

並使用Binding在條目容器和數據元素之間建立聯系。

例子:UI代碼如下:

 <StackPanel x:Name="stackPanel" Background="LightBlue">
        <TextBlock Text="Student ID:" FontWeight="Bold" Margin="5"/>
        <TextBox x:Name="textboxId" Margin="5"/>
        <TextBlock Text="Student List:" FontWeight="Bold" Margin="5"/>
        <ListBox x:Name="listBoxStudents" Height="110" Margin="5">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Path=Id}" Width="30"/>
                        <TextBlock Text="{Binding Path=Name}" Width="60"/>
                        <TextBlock Text="{Binding Path=Age}" Width="30"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>

C#代碼如下:

 public DC()
        {
            InitializeComponent();
            List<Student1> stuList = new List<Student1>()
            {
                new Student1(){Id=0,Name="Tim",Age=23},
                new Student1(){Id=1,Name="Tom",Age=24},
                new Student1(){Id=2,Name="Aom",Age=24},
                new Student1(){Id=3,Name="Bom",Age=24},
                new Student1(){Id=4,Name="Vom",Age=24},
                new Student1(){Id=5,Name="Tom",Age=24},
                new Student1(){Id=6,Name="Tom",Age=24},
            };
            this.listBoxStudents.ItemsSource = stuList;
            this.listBoxStudents.DisplayMemberPath = "Name";
            Binding binding = new Binding("SelectedItem.Id") { Source = this.listBoxStudents };
            this.textboxId.SetBinding(TextBox.TextProperty, binding);
        }

使用Xml數據作為Binding的源

.Net Framework提供了兩套處理XML數據的類庫:

符合DOM標准的類庫。特點:中規中矩,功能強大,但也背負了太多XML傳統和復雜。

以LINQ為基礎的類庫。特點:可以使用LINQ進行查詢和操作,方便快捷。

線性集合例子:

UI代碼如下:

 <StackPanel Background="LightBlue">
        <ListView x:Name="listViewStudents" Height="130" Margin="5">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Id" Width="80"
                                     DisplayMemberBinding="{Binding XPath=@Id}"/>
                    <GridViewColumn Header="Name" Width="120"
                                     DisplayMemberBinding="{Binding XPath=Name}"/>
                </GridView>
            </ListView.View>
        </ListView>
        <Button Content="Load" Margin="5,0" Click="Button_Click_1" Height="25"/>
    </StackPanel>

Xml代碼如下:

<?xml version="1.0" encoding="utf-8" ?>
<StudentList>
  <Student Id="1">
    <Name>Tom</Name>
  </Student>
  <Student Id="2">
    <Name>Tim</Name>
  </Student>
  <Student Id="3">
    <Name>Tqm</Name>
  </Student>
  <Student Id="4">
    <Name>Ter</Name>
  </Student>
  <Student Id="5">
    <Name>Te</Name>
  </Student>
  <Student Id="6">
    <Name>Tof</Name>
  </Student>
  <Student Id="7">
    <Name>Tf</Name>
  </Student>
</StudentList>

Button的Click處理事件代碼如下:

private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(@"D:\WORK\Program\7.5\Demo\WPF12.23\RamData.xml");
            XmlDataProvider xdp = new XmlDataProvider();
            xdp.Document = doc;
            xdp.XPath = @"/StudentList/Sudent";
            this.listViewStudents.DataContext = xdp;
            this.listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding());
        }

效果圖:

 

XML語言可方便的表示樹型數據結構,下面的例子是用TreeView控件來顯示有若干層目錄的文件系統。

這次把XML數據和XmlDataProvider對象直接寫在了Xaml代碼里,代碼中用到了HierarchicalDataTemplate類。

這個類具有ItemsSource屬性,由這種Template展示的數據是可以擁有子級集合的。代碼如下:

<Window.Resources>
        <XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder">
            <x:XData>
                <FileSystem xmlns="">
                    <Folder Name="Books">
                        <Folder Name="Windows">
                            <Folder Name="WPF"/>
                            <Folder Name="WCF"/>
                            <Folder Name="PDF"/>
                        </Folder>
                    </Folder>
                    <Folder Name="Tools">
                        <Folder Name="Develioment"/>
                        <Folder Name="WCF"/>
                        <Folder Name="PDF"/>
                    </Folder>
                </FileSystem>
            </x:XData>
        </XmlDataProvider>
    </Window.Resources>
    <Grid>
        <TreeView ItemsSource="{Binding Source={StaticResource xdp}}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}">
                    <TextBlock Text="{Binding XPath=@Name}"/>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </Grid>

效果如圖:

 

使用LINQ檢索結果作為Binding的源

LINQ查詢結果為IEnumerable<T>類型對象,而IEnumerable<T>又派生自IEnumerable,所以他可以作為列表控件的ItemsSource來使用。

先創建一個Student類

設計UI用於Button被點擊時顯示Student集合類型對象。

<StackPanel Background="LightBlue">
        <ListView x:Name="listViewStudents" Height="143" Margin="5">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Id" Width="60"
                                    DisplayMemberBinding="{Binding Id}"/>
                    <GridViewColumn Header="Name" Width="100"
                                    DisplayMemberBinding="{Binding Name}"/>
                    <GridViewColumn Header="Age" Width="80"
                                    DisplayMemberBinding="{Binding Age}"/>
                </GridView>
            </ListView.View>
        </ListView>
        <Button Content="Load" Click="Button_Click_1" Margin="5,0" Height="25"/>
 </StackPanel>

要從一個已經填充好的List<Student>中檢索出所有名字已字母‘T’開頭的學生,代碼如下:

 List<Student2> stuList = new List<Student2>()
            {
                new Student2(){Id=0,Name="Tim",Age=23},
                new Student2(){Id=1,Name="Tom",Age=24},
                new Student2(){Id=2,Name="Aom",Age=24},
                new Student2(){Id=3,Name="Bom",Age=24},
                new Student2(){Id=4,Name="Vom",Age=24},
                new Student2(){Id=5,Name="Tom",Age=24},
                new Student2(){Id=6,Name="Tom",Age=24},
            };
            this.listViewStudents.ItemsSource = from stu in stuList where stu.Name.StartsWith("T") select stu;

效果如圖:

 

使用ObjectDataProvider對象作為Binding的Source

ObjectDataprovider顧名思義就是把對象作為數據源提供給Binding。

前面還提到XmlDataProvider,也就是把XML數據作為數據源供給Binding。2個的父類都是DataSourceProvider抽象類。

現有一個Calu的類,具有計算加減乘除的方法:

 class Calu
    {
        public string Add(string a, string b)
        {
            double x = 0, y = 0, z = 0;
            if (double.TryParse(a, out x) && double.TryParse(b, out y))
            {
                z = x + y;
                return z.ToString();
            }
            return "input error";
        }
    }

這個例子需要實現的功能是在前2個TextBox輸入數字后,第3個TextBox顯示數字和。代碼如下:

 public LINQ()
        {
            InitializeComponent();
            Setbinding();
        }
private void Setbinding()
        {
            ObjectDataProvider odp = new ObjectDataProvider();
            odp.ObjectInstance = new Calu();
            odp.MethodName = "Add";
            odp.MethodParameters.Add("0");
            odp.MethodParameters.Add("0");
            Binding bdToArg1 = new Binding("MethodParameters[0]")
            {
                Source = odp,
                BindsDirectlyToSource = true,
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            };
            Binding bdToArg2 = new Binding("MethodParameters[1]")
            {
                Source = odp,
                BindsDirectlyToSource = true,
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            };
            Binding bdResult = new Binding(".") { Source = odp };
            this.textBoxArg1.SetBinding(TextBox.TextProperty, bdToArg1);
            this.textBoxArg2.SetBinding(TextBox.TextProperty, bdToArg2);
            this.textBoxResult.SetBinding(TextBox.TextProperty, bdResult);
        }

效果:

 

使用Binding的RelativeSource

RelativeSource屬性的數據類型為RelativeSource類,通過這個類的靜態和非靜態屬性我們可以控制它搜索相對數據源的方式。

下面這段代碼是多層布局控件內放置一個TextBox:

<Grid x:Name="g1" Background="Red" Margin="10">
        <DockPanel x:Name="d1" Background="Orange" Margin="10">
            <Grid x:Name="g2" Background="Yellow" Margin="10">
                <DockPanel x:Name="d2" Margin="10">
                    <TextBox x:Name="textBox1" Margin="10" FontSize="24"/>
                </DockPanel>
            </Grid>
        </DockPanel>      
    </Grid>

把TextBox的Text屬性關聯到Name屬性上。構造器里的代碼如下:

  public Relative()
        {
            InitializeComponent();
            RelativeSource rs = new RelativeSource(RelativeSourceMode.FindAncestor);
            rs.AncestorLevel = 1;
            rs.AncestorType=typeof(DockPanel);
            Binding binding = new Binding("Name") { RelativeSource=rs};
            this.textBox1.SetBinding(TextBox.TextProperty, binding);
        }

效果圖:

關聯自身Name屬性,代碼為:

 RelativeSource rs = new RelativeSource();
            rs.Mode = RelativeSourceMode.Self;
            Binding binding = new Binding("Name") { RelativeSource=rs};
            this.textBox1.SetBinding(TextBox.TextProperty, binding);

效果:

 

Binding對數據的轉換與校驗

ValidationRule類是個抽象類,在使用的時候要創建他的派生類並實現它的Validate方法。

Validate方法的返回值是ValidationResult類型對象,如果校驗通過就把IsValid屬性設為true,反之設為false並為ErrorContent屬性設置合適的消息內容。

下面這個程序是在UI上繪制一個TextBox和Slider,然后在后台C#代碼里使用Binding關聯起來,以Slider為源,TextBox為目標。

Slider的取值范圍是0-100,我們要驗證TextBox里的值是否為0-100.XAML代碼如下:

<StackPanel>
        <TextBox x:Name="textBox1" Margin="5"/>
        <Slider x:Name="slider1" Minimum="0" Maximum="100" Margin="5"/>
    </StackPanel>

准備一個ValidationRule的派生類:

 public class RangeValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            double d = 0;
            if (double.TryParse(value.ToString(), out d))
            {
                if (d >= 0 && d <= 100)
                {
                    return new ValidationResult(true, null);
                }
            }
            return new ValidationResult(false, "數據有誤");
            throw new NotImplementedException();
        }
    }

在構造器里建立Binding:

 public Valida()
        {
            InitializeComponent();
            Binding binding = new Binding("Value") { Source=this.slider1};
            binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            RangeValidationRule rvr = new RangeValidationRule();
            rvr.ValidatesOnTargetUpdated = true;
            binding.ValidationRules.Add(rvr);
            binding.NotifyOnValidationError = true;
            this.textBox1.SetBinding(TextBox.TextProperty, binding);

            this.textBox1.AddHandler(Validation.ErrorEvent,new RoutedEventHandler(this.VilidationError));
        }

效果:

           

Binding的數據轉換

Binding還有一種機制成為數據轉換(Data Convert)。

當Source端Path所關聯的數據與Target目標數據類型不一,可以添加數據轉換器(Data Converter)。

下面是一個Data Converter的綜合實例,程序用途是向玩家顯示飛機的狀態。

首先創建幾個自定義數據類型:

  //種類
    public enum Category
    {
        Bomber,Fighter
    }
    //狀態
    public enum State
    {
        Available,Locked,Unkuown
    }
    //飛機
    public class Plane
    {
        public Category Category { get; set; }
        public String Name { get; set; }
        public State State { get; set; }
    }

飛機的State屬性在UI被映射成CheckBox。所以我們要提供Converter,

將Category類型轉換成String類型,State類型和Bool類型的雙向轉換。代碼如下:

 public class CategoryToSourceConverter : IValueConverter
    {
        //將Catagory類型轉換為Uri
        public object Convert(object value, Type targetType, object parameter,CultureInfo culture)
        {
            Category c = (Category)value;
            switch (c)
            {
                case Category.Bomber: return @"\Icons\Bomber.png";
                case Category.Fighter: return @"\Icons\Fighter.png";
                default: return null;
            }
        }
        //不被調用
        public object ConvertBack(object value, Type targetType, object parameter,CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    public class StateToNullableBoolConverter : IValueConverter
    {
        //將State轉換為bool
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            State s = (State)value;
            switch (s)
            {
                case State.Available: return false;
                case State.Locked: return true;
                case State.Unkuown:
                default: return null;
            }
        }
        //將bool轉換為State
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            bool? nb = (bool?)value;
            switch (nb)
            {
                case true: return State.Available;
                case false: return State.Locked;
                case null:
                default: return State.Unkuown;
            }
        }
    }

下面看看怎么在XAML里消費這2個Converter,代碼如下:

 <Window.Resources>
        <local:CategoryToSourceConverter x:Key="cts"/>
        <local:StateToNullableBoolConverter x:Key="stnb"/>
    </Window.Resources>
    <StackPanel Background="LightBlue">
        <ListBox x:Name="listBoxPlane" Height="160" Margin=" 5">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Image Width="20" Height="20" Source="{Binding Path=Category,Converter={StaticResource cts}}"/>
                        <TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"/>
                        <CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource stnb}}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button x:Name="ButtonLoad" Content="Load" Width="50" Margin="5,0" Click="Button_Click_1"/>
        <Button x:Name="ButtonSave" Content="Save" Width="50" Margin="5,5" Click="ButtonSave_Click_1"/>
    </StackPanel>

Load按鈕的Click事件處理器負責把一組飛機的數據賦值給ListBox是ItemsSource屬性,Save按鈕的Click事件負責把修改后的數據寫入文件:

  private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            List<Plane> planelist = new List<Plane>
            {
                new Plane(){Category=Category.Bomber,Name="B-1",State=State.Unkuown},
                new Plane(){Category=Category.Bomber,Name="B-2",State=State.Unkuown},
                new Plane(){Category=Category.Fighter,Name="F-22",State=State.Unkuown},
                new Plane(){Category=Category.Fighter,Name="Su-47",State=State.Unkuown},
                new Plane(){Category=Category.Bomber,Name="B-52",State=State.Unkuown},
                new Plane(){Category=Category.Fighter,Name="J-10",State=State.Unkuown}
            };
            this.listBoxPlane.ItemsSource = planelist;
        }

        private void ButtonSave_Click_1(object sender, RoutedEventArgs e)
        {
            StringBuilder sb = new StringBuilder();
            foreach (Plane p in listBoxPlane.Items)
            {
                sb.AppendLine(string.Format("Category={0},Name={1},State={2}",p.Category,p.Name,p.State));
            }
            File.WriteAllText(@"D:\WORK\Program\7.5\Demo\WPF12.29\Icons\Planlist.txt", sb.ToString());
        }

效果:

 

MultiBinding(多路Binding)

有時候UI顯示的信息不止來自一個數據源,這時候就要用MultiBinding。

例:用戶注冊UI,第一、二個輸入用戶名三、四輸入Email要求一致,Button可用。

XAML代碼如下:

 <StackPanel Background="LightBlue">
        <TextBox x:Name="textbox1" Height="23" Margin="5"/>
        <TextBox x:Name="textbox2" Height="23" Margin="5,0"/>
        <TextBox x:Name="textbox3" Height="23" Margin="5"/>
        <TextBox x:Name="textbox4" Height="23" Margin="5,0"/>
        <Button x:Name="buuton1" Content="Submit" Width="80" Margin="5"/>
    </StackPanel>

用於設置MultiBinding的方法寫在SetMultiBinding里並在構造器里調用,代碼如下:

 public MultiBind()
        {
            InitializeComponent();
            this.SetMultiBinding();
        }
        private void SetMultiBinding()
        {
            //准備基礎綁定
            Binding b1 = new Binding("Text") { Source = this.textbox1 };
            Binding b2 = new Binding("Text") { Source = this.textbox2 };
            Binding b3 = new Binding("Text") { Source = this.textbox3 };
            Binding b4 = new Binding("Text") { Source = this.textbox4 };

            //准備MulitiBinding
            MultiBinding mb = new MultiBinding() {Mode=BindingMode.OneWay};
            mb.Bindings.Add(b1);//multibinding對add的順序是敏感的
            mb.Bindings.Add(b2);
            mb.Bindings.Add(b3);
            mb.Bindings.Add(b4);
            mb.Converter = new LogonMultiBindingConverter();

            //將Button與MultiBinding對象關聯
            this.buuton1.SetBinding(Button.IsEnabledProperty,mb);
        }

Converter代碼如下:

 public class LogonMultiBindingConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (!values.Cast<string>().Any(text => string.IsNullOrEmpty(text))
                &&values[0].ToString()==values[1].ToString()
                &&values[2].ToString()==values[3].ToString())
            {
                return true;
            }
            return false;
        }
        public object[] ConvertBack(object values, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

效果:

                

 

Binding這一章好長好長,知識點太多,基本上梳理完了。累啊!!!!

 


免責聲明!

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



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