1. 程式人生 > >WPF Data Binding之指定源(Source)的幾種方法【三】

WPF Data Binding之指定源(Source)的幾種方法【三】

    Binding的源是資料的來源,所以,只要一個物件包含資料並且能夠通過屬性將資料暴露出來,它就能當作Binding的源來使用。包含資料的物件比比皆是,但必須為Binding的Source指定合適的物件Binding才能正常工作。

1.1 沒有Source的Binding----使用DataContext作為資料來源

    在UI樹的每個節點都有DataContext屬性。Binding怎麼會自動向UI元素上一層查詢DataContext並把它作為自己的Source呢?其實,“Binding沿著UI元素樹向上找”只是WPF給我們的一個錯覺,Binding並沒有那麼智慧。之所以會這樣是因為DataContext是一個“依賴屬性”

,依賴屬性有一個很明顯的特點就是你沒有為某個控制元件的依賴屬性賦值的時候,控制元件會把自己容器的屬性值接過來當作自己的屬性值。實際上屬性值是沿著UI元素樹向下傳遞的。

讓我們看看下面的例子:

C#:

先建立一個名為Student的類,它具有ID,Name屬性:

public class Student2
{
    public int Id { set; get; }
    public string Name { set; get; }
}

/// <summary>
/// wnd636.xaml 的互動邏輯
/// </summary>
public partial class wnd636 : Window
{
    public wnd636()
    {
        InitializeComponent();
    }

    private void _btnOK_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show(_btnOK.DataContext.ToString());
    }
}

XAML:
<Window x:Class="WpfApplication6.wnd636"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication6"
        Title="wnd636" Height="130" Width="300">
    <StackPanel>
        <StackPanel.DataContext>
            <local:Student2 Id="36" Name="wndTom"/>
        </StackPanel.DataContext>
        <Grid >
            <StackPanel>
                <!--沒有指定Source,使用DataContext-->
                <TextBox Text="{Binding Path=Id}" Margin="5" BorderBrush="Black"/>
                <TextBox Text="{Binding Path=Name}" Margin="5" BorderBrush="Black"/>
                <Grid  DataContext="button33">
                    <Grid>
                        <!--沒有指定Button的DataContext,則會使用Grid的。向下傳遞!-->
                        <Button x:Name="_btnOK" Margin="5" Height="22" Click="_btnOK_Click"/>
                    </Grid>
                </Grid>                
            </StackPanel>
        </Grid>
    </StackPanel>    
</Window>

    外層StackPanel的DataContext進行了賦值----它是一個Student物件。2個TextBox通過外層StackPanel的DataContext進行了賦值----它是一個Student物件。2個TextBox通過Binding獲取值,但只為Binding指定了Path,沒有指定Source。



    在實際工作中,DataContext屬性值的運用非常的靈活。比如:

    當UI上的多個控制元件都使用Binding關注同一個物件變化的時候,不妨使用DataContext。當作為Source的物件不能被直接訪問的時候----比如B窗體內的控制元件想把A窗體裡的控制元件當作自己的Binding源時,但是A窗體內的控制元件可訪問級別是private型別,這是就可以把這個控制元件或者控制元件值作為窗體A的DataContext(這個屬性是Public級別的)這樣就可以暴露資料了。


    形象的說,這時候外層的資料就相當於一個數據的“至高點”,只要把元素放上去,別人就能夠看見。另外DataContext本身就是一個依賴屬性,我們可以使用Binding把它關聯到一個數據源上。

1.2 使用集合物件作為列表控制元件的ItemsSource

    WPF中的列表式控制元件都派生自ItemControl類,自然也繼承了ItemSource這個屬性。ItemSource可以接收一個IEnumerable介面派生類的例項作為自己的值(所有可被迭代遍歷的集合都實現了這個介面,包括陣列、List<T>等)。每個ItemControl都具有自己的條目容器Item Container,例如,ListBox的條目容器是ListBoxItem、Combox的條目容器是ComboxItem。ItemSource裡面儲存的是一條一條的資料,想要把資料顯示出來就要為資料穿上外衣,條目容器就起到了資料外衣的作用。這樣將資料外衣和它所對應的條目容器關聯起來呢?當然時依靠Binding!只要我們為一個ItemControl設定了ItemSource屬性值,ItemControl會自動迭代其中的資料元素,為每個資料元素準備一個條目容器,並使用Binding元素在條目容器和資料元素之間建立起關聯。

我們看看下面的例子:

XAML:

<Window x:Class="WpfApplication6.wnd637"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="wnd637" Height="200" Width="300">
    <StackPanel Margin="0,10,0,0">
        <TextBox x:Name="_txtBox" Height="23" />
        <ListView x:Name="_listView" Margin="5">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}" />
                    <GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
                </GridView>
            </ListView.View>
        </ListView>
    </StackPanel>
</Window>
    這裡我們有幾點需要注意的地方:
    從字面上來理解,ListView和GridView應該屬於同一級別的控制元件,實際上遠非這樣!ListView是ListBox的派生類而GridView是ViewBase的派生類,ListView中的View是一個ViewBase物件,所以,GridView可以做為ListView的View來使用而不能當作獨立的控制元件來使用。這裡使用理念是組合模式,即ListView有一個View,但是至於是GridView還是其它型別的View,由程式設計師自己選擇----目前只有一個GridView可用,估計微軟在這裡還會有擴充套件。

    其次,GridView的內容屬性是Columns,這個屬性是GridViewColumnCollection型別物件。因為XAML支援對內容屬性的簡寫,可以省略<GridView.Columns>這層標籤,直接在GridView的內容部分定義2個<GridViewColumn>物件,GridViewColumn中最重要的一個屬性是DisplayBinding(型別是BindingBase),使用這個屬性可以指定這一列使用什麼樣的Binding去關聯資料------這與ListBox有點不同,ListBox使用的是DisplayMemberPath屬性(型別是string)。如果想用更復雜的結構來表示這一標題或資料,則可為GridViewColumn設定HeadTemplate和CellTemplate,它們的型別都是DataTemplate。

C#:

// 在新增項、移除項或重新整理整個列表時,此集合將提供通知。
// 一般不實用List等
ObservableCollection<Student2> stuList = new ObservableCollection<Student2>()
{
    new Student2(){Id = 1, Name="Tim"},
    new Student2(){Id = 2, Name="Job"},
    new Student2(){Id = 3, Name="Jack"},
    new Student2(){Id = 4, Name="Toky"},
};

_listView.ItemsSource = stuList;
// 可以使用LINQ語言查詢
//_listView.ItemsSource = from stu in stuList where stu.Name.StartsWith("T") select stu;

// 顯示選擇的ID
_txtBox.SetBinding(TextBox.TextProperty, new Binding("SelectedItem.Id") 
{
    Source = _listView ,
    Mode = BindingMode.OneWay,
});

    在使用集合型別的資料作為列表控制元件的ItemSource時一般會考慮使用ObservableCollection<T>替換List<T>,因為ObservableCollection<T>類實現了INotifyChange和INotifyPropertyChanged介面,能把集合的變化立刻通知顯示到它的列表控制元件上,改變會立刻顯示出來。

1.3 使用XML資料作為Binding的源

    在使用XML資料作為Binding的Source的時候我們將使用XPath屬性而不是Path屬性來指定資料的來源。

下面的XML文字是一組文字資訊,我們要把它顯示在一個ListView控制元件裡:

<?xml version="1.0" encoding="utf-8" ?>
<StudentList>
  <Student Id ="1">
    <Name>Tom</Name>
  </Student>
  <Student Id ="2">
    <Name>Jack</Name>
  </Student>
  <Student Id ="3">
    <Name>Joe</Name>
  </Student>
</StudentList>

XAML:
<Window x:Class="WpfApplication6.wnd639"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="wnd639" Height="200" Width="300">
    <StackPanel>
        <ListView x:Name="_listView">
            <ListView.View>
                <GridView>
                    <[email protected]表示Attribute-->
                    <GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding XPath = @Id}"/>
                    <!--表示子集元素-->
                    <GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding XPath = Name}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </StackPanel>
</Window>

C#:
// 將XML賦值給它
XmlDataProvider xdp = new XmlDataProvider();
xdp.Source = new Uri(@"E:\RefCode\C#\WPF\深入淺出WPF\第六章Binding\WpfApplication6\WpfApplication6\stuData.xml");
xdp.XPath = @"StudentList/Student";
_listView.DataContext = xdp; // 自動查詢Source
_listView.SetBinding(ListView.ItemsSourceProperty, new Binding());

// 也可以使用LINQ查詢
//XDocument xDoc = XDocument.Load(@"E:\RefCode\C#\WPF\深入淺出WPF\第六章Binding\WpfApplication6\WpfApplication6\stuData.xml");
//_listView.ItemsSource = from ele in xDoc.Descendants("Student")
//                        where ele.Element("Name").Value.StartsWith("T")
//                        select new Student()
//                            {
//                                Id = int.Parse(ele.Attribute("Id").Value),
//                                Name = ele.Element("Name").Value,
//                            };


    XAML最關鍵的兩句:DisplayMemberBinding="{Binding [email protected]}"和DisplayMemberBinding="{Binding XPath=Name}",他們分別為GridView兩列指定了要關注的XML路徑----很明顯,使用@符號加字串表示的是XML元素的Attribute,不加@符號表示的是子級元素

1.4 使用ObjectDataProvider作為binding的Source

    理想情況下,上游程式設計師將類設計好、使用屬性把資料暴露出來,下游程式設計師將這些類作為Binding的Source、把屬性作為Binding的Path來消費這些類。但很難保證一個類的屬性都用屬性暴露出來,比如我們需要的資料可能是方法的返回值。而重新設計底層類的風險和成本會比較高,況且黑盒引用類庫的情況下我們不可能更改已經編譯好的類,這時候需要使用ObjectDataProvider來包裝做為Binding源的資料物件了。ObjcetDataProvider 顧名思義就是把物件作為資料來源提供給Binding。

    ObjectDataProvider物件和它被包裝的物件關係如下圖:


    瞭解了ObjectDataProvider的使用方法,我們看看如何把它當作Binding的Source來使用,這個程式實現的功能是,我在前兩個TextBox裡面輸入值的時候,第三個TextBox會顯示前兩個文字框裡面相加之和。

XAML:

<Window x:Class="WpfApplication6.wnd6311"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="wnd611" Height="200" Width="300">
    <StackPanel>
        <TextBox x:Name="_txtBox1" Margin="5" />
        <TextBox x:Name="_txtBox2" Margin="5" />
        <TextBox x:Name="_txtBox3" Margin="5" />
    </StackPanel>
</Window>

C#:
    public class Calculate
    {
        public string Add(string str1, string str2)
        {
            double val1 = 0;
            double val2 = 0;

            if(double.TryParse(str1, out val1) && double.TryParse(str2, out val2))
            {
                return (val1 + val2).ToString();
            }
            return("Error");
        }
    }

    /// <summary>
    /// wnd6311.xaml 的互動邏輯
    /// </summary>
    public partial class wnd6311 : Window
    {
        public wnd6311()
        {
            InitializeComponent();

            ObjectDataProvider odp = new ObjectDataProvider();
            odp.ObjectInstance = new Calculate();
            odp.MethodName = "Add";
            // MethodParameters是型別敏感的,相當於告訴引數為兩個string型別
            odp.MethodParameters.Add("0");
            odp.MethodParameters.Add("0");

            _txtBox1.SetBinding(TextBox.TextProperty, new Binding("MethodParameters[0]")
            {
                Source = odp,
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                BindsDirectlyToSource = true,
                //Mode = BindingMode.OneWayToSource, // MethodParameters為非依賴屬性      
            });
            _txtBox2.SetBinding(TextBox.TextProperty, new Binding("MethodParameters[1]")
            {
                Source = odp,
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                BindsDirectlyToSource = true,
                //Mode = BindingMode.OneWayToSource, // MethodParameters為非依賴屬性
            });
            _txtBox3.SetBinding(TextBox.TextProperty, new Binding(".")
            {
                Source = odp,                
            });
        }
    }


    一般情況下資料從那裡來,哪裡就是Binding的Source,資料到哪裡去,哪裡就是Binding 的Target。按這個理論,前兩個TextBox應該是ObjcetDataProvider的源,而ObjcetDataProvider物件又是最後一個TextBox的源。但實際上,三個TextBox都以ObjcetDataProvider作為資料來源,只是前兩個在資料流向上做了限制,這樣做的原因不外乎有兩個:
1、ObjcetDataProvider的MethodParameter不是依賴屬性,不能作為Binding的目標
2、資料驅動UI理念要求我們儘可能的使用資料物件作為Binding的Source而把UI當做Binding的Target。

1.5 使用Binding的RelativeSource

    當一個Binding有明確的來源的時候,我們可以通過Source或者ElementName賦值的辦法讓Binding與之關聯。有些時候我們不能確定作為Source物件叫什麼名字,但是我們知道它與做為Binding目標物件在UI上的相對關係,比如控制元件自己關聯自己的某個資料,關聯自己某級容器的資料,這時候就需要用到Binding的RelativeSource屬性。


    RelativeSource屬性的型別是RelativeSource類,通過這個類的幾個靜態或者非靜態的屬性我們可以控制它搜尋相對資料來源的方式。

看下面的例子:

XAML:

<Window x:Class="WpfApplication6.wnd6312"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="wnd6312" Height="200" Width="300">
    <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" Background="LawnGreen" Margin="10">
                    <TextBox x:Name="_t1" Margin="10"/>
                </DockPanel>
            </Grid>
        </DockPanel>
    </Grid>
</Window>
C#:
    /// <summary>
    /// wnd6312.xaml 的互動邏輯
    /// </summary>
    public partial class wnd6312 : Window
    {
        public wnd6312()
        {
            InitializeComponent();

            // 繫結具有相對關係的控制元件
            // 繫結上級元素
            // 型別為Grid
            // 層級為1,即上級節點的序號位置
            _t1.SetBinding(TextBox.TextProperty, new Binding("Name")
            {
                RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Grid), 1),
            });

            // 繫結自身
            //_t1.SetBinding(TextBox.TextProperty, new Binding("Name")
            //{
            //    RelativeSource = new RelativeSource(RelativeSourceMode.Self),
            //});
        }
    }


    AncestorLevel屬性指定的是以Binding目標控制元件為起點的層級偏移量---gd2的偏移量是1,gd2的偏移量是2,依次類推。AncestorType屬性告訴Binding去找什麼型別的物件作為自己的源,不是這個型別的物件會被跳過。上面這段程式碼的意思是告訴Binding從自己的第一層依次向外找,找到第一個Grid型別物件後把它當作自己的源。

參考:《深入淺出WPF》

相關推薦

WPF Data Binding指定(Source)的方法

    Binding的源是資料的來源,所以,只要一個物件包含資料並且能夠通過屬性將資料暴露出來,它就能當作Binding的源來使用。包含資料的物件比比皆是,但必須為Binding的Source指定合

數據綁定()為Binding指定綁定方法

styles source 控件 none tex 內部 是把 單個 AR 原文:數據綁定(三)為Binding指定綁定源的幾種方法 Binding的源是數據的來源

Unity查詢物件的方法

GameObject.FindObjectsOfType(typeof(型別)) as 型別[];  GameObject.FindObjectsOfType<型別>(); GameObject.FindObjectsOfTypeAll(typeof(型別[]))as 型別[];此方法在新版本

Linux下新增方法

sudo add-apt-repository  xxxxxxxsudo gedit /etc/apt/sources.list然後把源貼上進去,儲存,最後shell輸入sudo apt-get up

獲取spring的ApplicationContext方式

throw 不能 在服務器 tap sync gconf 總結 title imp 轉自:http://blog.sina.com.cn/s/blog_9c7ba64d0101evar.html Java類獲取spring 容器的bean常用的5種獲取spring 中bea

Unity中自動尋路的方法

    現在的大部分mmo遊戲都有了自動尋路功能。點選場景上的一個位置,角色就會自動尋路過去。中間可能會有很多的障礙物,角色會自動繞過障礙物,最終達到終點。使用Unity來開發手遊,自動尋路可以有很多種實現方式。第一種比較傳統的是使用A星尋路,它是一種比較傳統的人工智慧演算

C語言巨集替換的用法 轉載

①簡單巨集替換             #defind   Pi   3.14159     或           #ifndef   __THIS_FILE__           #define   __THIS_FILE__     //   用於防止重複包含檔案               ……  

Nodejs中控制非同步的方法陸續更新……

Promise是非同步程式碼實現控制流的一種方式。這一方式可以讓你的程式碼乾淨、可讀並且健壯。 Q 和 Bluebird 都是在實現 Promise A+ 標準的基礎上提供了一些封裝和幫助方法 1

Mysql的搜尋引擎轉載

MySQL資料庫引擎取決於MySQL在安裝的時候是如何被編譯的。要新增一個新的引擎,就必須重新編譯MYSQL。在預設情況下,MYSQL支援三個引擎:ISAM、MYISAM和HEAP。另外兩種型別INNODB和BERKLEY(BDB),也常常可以使用。如果技術高超,還可以使用M

翻譯搬運起源引擎網路同步模型(Source Multiplayer Networking)

寫在前面 文章的原文很多地方都能找到 也有人做過優秀翻譯 【騰訊GAD譯館】 Source引擎多人模式網路同步模型,我只是因為自己注意力渙散覺得文章乾澀不寫下來就看不動,也請閱讀的大家不要用我的渣翻水平做橫向對比,取得必要知識資訊就好。 【2018

Android 實現滑動的方法)scrollTo 與 scrollBy

scrollTo(x,y): 表示移動到一個座標點(x,y) scrollBy(dx,dy) : 表示移動的增量為dx,dy 如果在ViewGroup中使用scrollTo和scrollBy,那麼移

WPF應用BindingItemsSource

一、ListBox顯示簡單的列表資訊(未使用DataTemplate) (1) Student類 public class Student { public string Name { get; set; } public

WPF應用Binding資料轉換

有時資料來源的型別與目標型別不一致時,需要把源通過一定的轉換之後才能繫結到目標之上。 本例: (1) 把車的型別轉換成車所對應的圖片路徑; (2) 把車的執行狀態轉換成CheckBox的狀態; 1、類/型別定義 public enum Category//車的型

WPF應用BindingMultiBinding

有時目標繫結多個源的資料,此時需要使用多路繫結MultiBinding。 本例4個TextBox(textBox1, textBox2, textBox3, textBox4)及一個按鈕,當textBox1與textBox2內容相同,並且textBox3與textBox4

python爬蟲--解析網頁方法正則表達式

ima 3.5 ref string tex href quest user lin 1、正則表達式 正則表達式是一個特殊的字符序列,它能幫助你方便的檢查一個字符串是否與某種模式匹配。 re 模塊使 Python 語言擁有全部的正則表達式功能。 re.match函數 re.

python爬蟲--解析網頁方法BeautifulSoup

first div xml html find 抓取 XML 格式 速度慢 析取 一.解析器概述 soup=BeautifulSoup(response.body) 對網頁進行析取時,並未規定解析器,此時使用的是python內部默認的解析器“html.parser”

重構--重新組織函數的方法

methods nor com owin 你會 banner 擔心 ont logs 重構手法中,很大一部分都是在對函數進行整理,很多問題也都來自Long Methods(過長的函數),下邊就介紹一下關於重新組織函數的幾種常用手法 1 Extract Method(提煉函數

spring-boot-2.0.3不一樣系列碼篇 - run方法createApplicationContext,絕對有值得你看的地方

處理 gen 利用反射 mva 默認 .get war close -s 前言   此系列是針對springboot的啟動,旨在於和大家一起來看看springboot啟動的過程中到底做了一些什麽事。如果大家對springboot的源碼有所研究,可以挑些自己感興趣或者對自己有

JVM記憶體溢位的情況以及可以採取的解決方案

開發中遇到過以下三種記憶體溢位的狀況: 一、 java.lang.OutOfMemoryError: Java heap space 二、 java.lang.OutOfMemoryError: PermGen space 三、 java.lang.OutO

servlet九大內建物件response 的contentType 型別

引言: 在Http請求中,我們每天都在使用Content-type來指定不同格式的請求資訊,但是卻很少有人去全面瞭解content-type中允許的值有多少,這裡將講解Content-Type的可用值,以及在spring MVC中如何使用它們來對映請求資訊。 1.  Cont