GSI

Where does a Binding find its data?

If you’ve look at much WPF Xaml you’ve probably seen bindings like this:

 

<TextBlock Text="{Binding Name" />

 

… which binds the Text property of the TextBlock to the Name property of some data object.

 

The question that begets is:  where does the data come from?  The rest of this post looks at the answer.

 

 

Properties on Binding

 

The Binding class has a few properties that provide ways to let you set the source of the data onto the Binding, depending on what works best for your situation.   Binding looks like this:

 

public class Binding : BindingBase

{

    ...

    public object Source { get; set; }

    public string ElementName { get; set; }

    public RelativeSource RelativeSource { get; set; }

    ...

}

 

 

The Source Property

 

The most straightforward way to set the source of the Binding is to use the Source property.  One of the more common ways to do that is to reference the data out of a resource dictionary:

 

<Grid>

  <Grid.Resources>

    <Int32Collection x:Key='DataSource1'>1,2,3</Int32Collection>

  </Grid.Resources>

 

  <ListBox ItemsSource='{Binding Source={StaticResource DataSource1}}' />

</Grid>

 

(This example creates a ListBox with 3 entries in it, labeled “1”, “2”, and “3”.)

 

You can be even more explicit by setting the Source directly, specifying the Binding in full Xml syntax (rather than the above markup extension syntax):

 

<Grid>

  <ListBox>

    <ListBox.ItemsSource>

      <Binding>

        <Binding.Source>

          <Int32Collection>1,2,3</Int32Collection>

        </Binding.Source>

      </Binding>

    </ListBox.ItemsSource>

  </ListBox>

</Grid>

 

You can also set the Binding.Source by referencing a static member:

 

namespace MyNamespace

{

    class MyClass

    {

        public static List<int> MyData;

        static MyClass()

        {

            MyData = new List<int>();

            MyData.Add(1);

            MyData.Add(2);

            MyData.Add(3);

        }

 

    }

}

 

 

<Page

    xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'

    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'

    xmlns:my="clr-namespace:MyNamespace" >

 

  <ListBox ItemsSource='{Binding Source={x:Static my:MyClass.MyData}}' />

 

</Page>

 

 

The ElementName Property

 

Aside from the Binding.Source property, you can also reference your data by name, using Binding.ElementName, such as:

 

<StackPanel>

  <TextBox Name='TextBox1' />

  <Button Content='{Binding Text, ElementName=TextBox1}' />

</StackPanel>

 

(This example causes the button label to be whatever you type into the text box.)

 

 

The RelativeSource Property

 

The RelativeSource property provides a way to tell the Binding to look around where it’s used to find its source.  For example, the ‘Self’ RelativeSource binds to the object on which the Binding is placed, such as in the following (which creates a TextBlock that says “Hi”):

 

<TextBlock Tag='Hi' Text='{Binding Tag, RelativeSource={RelativeSource Self}}' />

 

As another example, the following binds the TextBlock.Text property to the Grid in its ancestry (again displaying “Hi” in this case):

 

<Grid Tag='Hi'>

 

  <Border Background='LightGray'>

    <TextBlock

         Text='{Binding Tag, RelativeSource={RelativeSource FindAncestor, AncestorType=Grid}}' />

  </Border>

 

</Grid>

 

The other two modes of a RelativeSource are TemplatedParent and PreviousData.

 

Explicit DataContext Property

 

A common way the Binding can get its source is via a DataContext property set on an element (on the element itself, not on the Binding).  For (a rather boring) example, this creates a Button that says “Click”:

 

<Button Content='{Binding}'>

  <Button.DataContext>

    <sys:String>Click</sys:String>

  </Button.DataContext>

</Button>

 

This gets more interesting and typical if you set the DataContext somewhere else, usually on the root of the page/window.  That works because the DataContext property inherits.  The following example shows that by setting the DataContext onto the root.  In this case, it is set to be an XmlDataProvider which is querying a Yahoo web service for the weather in Barrow, Alaska.  Since the DataContext inherits, Bindings throughout the page have access to it automatically.  Note also in this case that since we’re binding to XML, the Bindings are using XPath syntax.

 

<Page   

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 

    >

 

  <Page.DataContext>

    <XmlDataProvider

            Source="http://xml.weather.yahoo.com/forecastrss?p=99723" 

            XPath="/rss/channel"/>

  </Page.DataContext>

 

  <Border BorderBrush="Black"

          BorderThickness="1" Width="370" Height="170" CornerRadius="6">

    <StackPanel>

 

      <!-- Image for "Yahoo! News" -->

      <Image Margin="15,15,0,0"

                   Stretch="None" 

                   HorizontalAlignment="Left"

                   Source="{Binding XPath=image/url}" />

 

      <!-- Text to say e.g. "Yahoo! Weather - Bellevue, WA", with a hyperlink to a

           detailed weather page -->

      <TextBlock Margin="15,15,0,0">

        <Hyperlink NavigateUri="{Binding XPath=item[1]/link}">

          <TextBlock Text="{Binding XPath=title}"/>

        </Hyperlink>

      </TextBlock>

 

      <!-- Text to say e.g. "Conditions for Belleveue, WA at 9:53am ..." -->

      <TextBlock FontWeight="Bold"    Margin="15,15,0,0"

                 Text="{Binding XPath=item[1]/title}"/>

 

      <!-- Weather details  -->

      <TextBlock Margin="15,0,0,0">

 

        <!-- Text to say current condition and temp -->

        <TextBlock>

          <TextBlock.Text>

            <Binding >

              <Binding.XPath>

                item[1]/*[local-name()="condition" and

                namespace-uri()="http://xml.weather.yahoo.com/ns/rss/1.0"]/@text

              </Binding.XPath>

            </Binding>

          </TextBlock.Text>

        </TextBlock>,

        <TextBlock>

          <TextBlock.Text>

            <Binding >

              <Binding.XPath>

                item[1]/*[local-name()="condition" and

                namespace-uri()="http://xml.weather.yahoo.com/ns/rss/1.0"]/@temp

              </Binding.XPath>

            </Binding>

          </TextBlock.Text>

        </TextBlock>°

        <TextBlock>

          <TextBlock.Text>

            <Binding >

              <Binding.XPath>

                *[local-name()="units" and

                namespace-uri()="http://xml.weather.yahoo.com/ns/rss/1.0"]/@temperature

              </Binding.XPath>

            </Binding>

          </TextBlock.Text>

        </TextBlock>

        <LineBreak/>

 

        <!-- Text to say sunrise/sunset times -->

        Sunrise:

        <TextBlock>

          <TextBlock.Text>

            <Binding >

              <Binding.XPath>

                *[local-name()="astronomy" and

                namespace-uri()="http://xml.weather.yahoo.com/ns/rss/1.0"]/@sunrise

              </Binding.XPath>

            </Binding>

          </TextBlock.Text>

        </TextBlock>, sunset:

 

        <TextBlock>

          <TextBlock.Text>

            <Binding >

              <Binding.XPath>

                *[local-name()="astronomy" and

                namespace-uri()="http://xml.weather.yahoo.com/ns/rss/1.0"]/@sunset

              </Binding.XPath>

            </Binding>

          </TextBlock.Text>

        </TextBlock>

      </TextBlock>

    </StackPanel>

 

  </Border>

</Page>

 

… Sunrise at midnight, sunset at noon:

 

Attachment: barrow.jpg (11494 bytes)

 

 

Implicit DataContext

 

Finally, there are a couple of places where the DataContext gets set automatically for you.  That usually happens with ContentControl (e.g. Button) and ItemsControl (e.g. ListBox). 

 

Here’s a ContentControl example.  In this markup we have a DataTemplate set up in a ResourceDictionary so that it will be used for any ContentControl that has a String as its content.  So it’s automatically picked up in the subsequent Button.  In that DataTemplate, we have a binding to the data item, which in this case is the ContentControl.Content.  Therefore what we get here is a Button that says “Click” in bold italic.

 

<Page   

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 

    xmlns:sys="clr-namespace:System;assembly=mscorlib" >

 

  <Page.Resources>

    <DataTemplate DataType="{x:Type sys:String}">

      <!-- The Text property is bound to the Content property of

           whatever ContentControl with which this DataTemplate is used -->

      <TextBlock Text='{Binding}' FontStyle='Italic' FontWeight='Bold' />

    </DataTemplate>

  </Page.Resources>

 

  <Button>Click</Button>

 

</Page>

 

 

And here’s an ItemsControl example.  In this markup, we have a ListBox bound to an integer collection again (using the inherited DataContext as the source), but this time we have an item template, which is a  template to use when displaying the items in the ListBox.  And within that item template, the DataContext is again automatically set to be the item

 

<Page   

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 

    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml >

 

  <Page.DataContext>

    <Int32Collection>1,2,3</Int32Collection>

  </Page.DataContext>

 

  <!-- ItemsSource is bound to the DataContext (Int32Collection) -->

  <ListBox ItemsSource='{Binding}' >

    <ListBox.ItemTemplate>

      <DataTemplate>

 

        <DataTemplate.Triggers>

          <!-- Within the DataTemplate, the DataContext is set to be the data item.

               So this DataTrigger.Binding property's DataContext is going to be

               the item (1, 2, or 3) -->

          <DataTrigger Binding='{Binding}' Value='2'>

            <Setter Property='TextBlock.FontStyle' Value='Italic' />

            <Setter Property='TextBlock.FontWeight' Value='Bold' />

          </DataTrigger>

        </DataTemplate.Triggers>

 

        <Border Padding='10'>

          <!-- Similarly this Text property is bound to the item (1, 2, or 3) -->

          <TextBlock Text='{Binding}' />

        </Border>

      </DataTemplate>

    </ListBox.ItemTemplate>

  </ListBox>

</Page>

Posted by gsi
:

본 내용은 msdn에 있는
How to: Control When the TextBox Text Updates the Sources 에 대한 내용을
조금 나름대로 이해 할 수 있도록 정리한 것입니다.
영어는 잘 몰라서 번역 수준은 아닙니다.


TextBox의 Text는 기본적으로 UpdateSourceTrigger 의 값이 LostFocus로 되어 있다고 합니다. 그래서 Text를 다 적고 나서 마우스나 기타 다른 것을 통해서 Focus가 다른 데로 갔을때 업데이트가 되도록 되어 있습니다.

하지만 이 것을 Text를 입력 하고 있는 중간에 계속 해서 업데이트가 가능하게 할려면
UpdateSourceTrigger를 수정해줘야 하는거 같습니다.

즉, TextBox 의 Binding 속성을 사용해서 UpdateSourceTrigger의 값을 PropertyChanged로 수정해주어야 되는거 같네요.

블렌드에서 TextBox와 TextBlock을 사용해서 값을 Data Binding 해서 테스트를 해보면
값이 바뀌는것을 볼 수 있습니다.

하지만 지금 예제는 cs 파일을 통해서 연동하는 부분을 살펴 보게 되겠습니다.

Person.cs 파일을 하나 생성하게 되는데요.

1. 네임 스페이스 지정
using System.ComponentModel;

2. INotifyPropertyChanged를 상속 받는 클래스 생성
class Person : INotifyPropertyChanged
{
}


3. 데이터 값으로 name를 생성
private string name;

4. PropertyChangedEventHandler 이벤트 핸들 생성
public event PropertyChangedEventHandler PropertyChanged;

5. name를 사용할 인터페이스(??)를 생성, get, set 두개 지정
public string PersonName
{
    get { return name; }
    set
    {
        name = value;

        OnPropertyChanged("PersonName");
    }
}


6. PropertyChanged 를 처리할 함수 생성
protected void OnPropertyChanged(string name)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        handler(this, new PropertyChangedEventArgs(name));
    }
}

cs 파일을 만든 다음에 xaml을 처리 합니다.
1. Person.cs 파일을 사용하기 위해서 네임 스페이스를 지정
xmlns:src="clr-namespace:UntitledProject1"

2. Window.Resources 에 Person의 키를 생성
<src:Person x:Key="myDataSource" PersonName="Joe"/>
 - x:Key="myDataSource" 를 하게 되면 Person의 생성자가 호출되는거 같네요.
 - PersonName="Joe" 를 하게 되면 public string PersonName 의 set 함수가
호출되는거 같습니다.

3. TextBox의 Text 속성에 바인딩 시킵니다.
<TextBox.Text>
    <Binding Source="{StaticResource myDataSource}" Path="PersonName"
                   UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
 - Source에는 Window.Resource에 Person을 선언해 놓은 myDataSource를 적용합니다. Path에는 PersonName를 추가 하게 되는데요. PersonName는 Person.cs 파일에 존재 하는 것입니다. (만약 xml을 사용하게 된다면 Path가 아니라 XPath가 되며 "PersonName"이 아니라 @"PersonName"가 됩니다.)
 - UpdateSourceTrigger="PropertyChanged"를 셋팅해 주게 됩니다.

4. TextBlock 의 Text 속성에 바인딩 시킵니다.
<TextBlock Text="{Binding Source={StaticResource myDataSource}, Path=PersonName}"/>
 - UpdateSourceTrigger 는 지정하지 않고 TextBox와 동일하게 구성합니다.
 - 위의 코드는 아래와 같이도 쓸수 있겠네요.
<TextBlock>
    <TextBlock.Text>
        <Binding Source="{StaticResource myDataSource}" Path="PersonName"/>
    </TextBlock.Text>
</TextBlock>


-----------------------------------------------------
작동 흐름을 대충 찍어 보면..

1. <src:Person x:Key="myDataSource" PersonName="Joe"/>
여기를 거치게 되면서
- public Person() 생성자가 호출 됩니다.
- public string PersonName 의 set에 value로 "Joe"가 입력 됩니다.
- OnPropertyChanged("PersonName"); 가 호출됩니다.
- OnPropertyChanged 내부에서는 handler가 null 이라서 그냥 빠져 나가네요.

2. Path="PersonName"
- 이 구문을 만나게 되면서 public string PersonName 이 호출되고 여기에서 name의 값을 get으로 리턴 시켜 줍니다.

3. TextBox의 Text에 내용 입니다.
- public string PersonName 의 set 가 호출 됩니다.
- OnPropertyChanged("PersonName"); 를 호출합니다.
- OnPropertyChanged() 내부에서 handler가 null이 아니게 되며, handler()를 호출하게 됩니다.
- public string PersonName 가 호출 되게 되는데요.. 음 아무래도 여기서, xaml의 코드를 거쳐서 TextBlock의 Text의 값이 변하게 되는거 같습니다.

**이상 간단하게 봤습니다.**

WPF를 하면서 C#의 스킬 부족과 영어 울렁증, xaml의 코드 난해함에.. 많이 헤매게 되는거 같습니다. 빨리 익혀서 이것저것 만들어 보는 날이 왔으면 좋겠네요 ^^.

강좌가 조금 난잡해 진거 같은데요.. ^^..
본 강좌의 소스는 인터넷의 msdn 사이트나 msdn을 설치 하셨다면 아래 주소에 있습니다.^^
ms-help://MS.MSDNQTR.v90.en/wpf_samples/html/b2906631-8a4b-43b4-b077-9e732a8ff363.htm (로컬msdn)

잘못된 부분이나 조금 도움이 될만한 내용이 있으시다면 적어 주시면 고맙겠습니다. ^^

Posted by gsi
: