[WPF] RelativeSource

C#/WPF 2020. 7. 19. 23:28
728x90

1. Self

{Binding RelativeSource={RelativeSource Self}, Path=Height}

: 자기 자신을 참조하는 것으로 위 내용은 자기 자신의 Height 값을 바인딩합니다.

 

2. FindAncestor

{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}, AncestorLevel=2},Path=Name}

: 부모를 찾아 바인딩하는 것으로 위 내용은 부모 중 Border 타입을 찾는데 2번째 부모를 찾아 Name 값을 바인딩합니다. (1이면 Border 타입중 바로 자기 상위 부모를 찾습니다.)

 

3. TemplatedParent

<Ellipse Height="100" Width="150" Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}"/>

: 위 항목이 Style 에 정의 되어있는 경우 해당 Style 을 사용하는 항목의 요소를 변경한다.

  위내용은 원을 그릴때 해당 Style 를 사용하는 컨트롤의 BackGround 색으로 원을 체웁니다.

 

4. PreviousData

{Binding RelativeSource={RelativeSource PreviousData},Path=Value}

: 이전값을 가지는 것으로 위 내용은 이전 값의 Value 값을 바인딩 합니다.

 

 

참고 : https://www.c-sharpcorner.com/UploadFile/yougerthen/relativesources-in-wpf/

 

아래는 백업 용도 ---

 

A lot of articles those are talking about binding and sources, and how to bind properties each other using StaticResources, DynamicResources, although you can find information about the RelativeSource and its use cases but not with more details even in Microsoft documentations. In this article, I will expose the use cases of the RelativeSources in WPF.

The RelativeSource is a markup extension that is used in particular binding cases when we try to bind a property of a given object to another property of the object itself, when we try to bind a property of a object to another one of its relative parents, when binding a dependency property value to a piece of XAML in case of custom control development and finally in case of using a differential of a series of a bound data. All of those situations are expressed as relative source modes. I will expose all of those cases one by one.

1. Mode Self:

Imagine this case, a rectangle that we want that its height is always equal to its width, a square let's say. We can do this using the element name

<Rectangle Fill="Red" Name="rectangle"
                   
 Height="100" Stroke="Black"
                   
 Canvas.Top="100" Canvas.Left="100"
                    Width="{Binding ElementName=rectangle,
                    Path=Height}"/>

But in this above case we are obliged to indicate the name of the binding object, namely the rectangle. We can reach the same purpose differently using the RelativeSource

<Rectangle Fill="Red" Height="100"
                  
 Stroke="Black"
                  
 Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Height}"/>

For that case we are not obliged to mention the name of the binding object and the Width will be always equal to the Height whenever the height is changed.

If you want to parameter the Width to be the half of the height then you can do this by adding a converter to the Binding markup extension.
Let's imagine another case now:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Parent.ActualWidth}"/>

The above case is used to tie a given property of a given element to one of its direct parent ones as this element holds a property that is called Parent. This leads us to another relative source mode which is the FindAncestor one.

2. Mode FindAncestor

In this case, a property of a given element will be tied to one of its parents, Of Corse. The main difference with the above case is the fact that, it's up to you to determine the ancestor type and the ancestor rank in the hierarchy to tie the property. By the way try to play with this piece of XAML

<Canvas Name="Parent0">
        <Border Name="Parent1"
                 Width="{Binding RelativeSource={RelativeSource Self},
                 Path=Parent.ActualWidth}"
                 Height="{Binding RelativeSource={RelativeSource Self},
                 Path=Parent.ActualHeight}">
            <Canvas Name="Parent2">
                <Border Name="Parent3"
                Width="{Binding RelativeSource={RelativeSource Self},
               Path=Parent.ActualWidth}"
               Height="{Binding RelativeSource={RelativeSource Self},
                  Path=Parent.ActualHeight}">
                   <Canvas Name="Parent4">
                   <
TextBlock FontSize="16"
                   Margin="5" Text="Display the name of the ancestor"/>
                   <TextBlock FontSize="16"
                     
Margin="50"
                
Text="{Binding RelativeSource={RelativeSource 
                           FindAncestor
,
                           
AncestorType={x:Type Border},
                           AncestorLevel=2},Path=Name}"
                           
Width="200"/>
                    </Canvas>
                </Border>
            </Canvas>
        </Border>
    </Canvas>

The above situation is of two TextBlock elements those are embedded within a series of borders and canvas elements those represent their hierarchical parents. The second TextBlock will display the name of the given parent at the relative source level.

So try to change AncestorLevel=2 to AncestorLevel=1 and see what happens. Then try to change the type of the ancestor from AncestorType=Border to AncestorType=Canvas and see what's happens.

The displayed text will change according to the Ancestor type and level. Then what's happen if the ancestor level is not suitable to the ancestor type? This is a good question, I know that you're about to ask it. The response is no exceptions will be thrown and nothings will be displayed at the TextBlock level.

3. TemplatedParent

This mode enables tie a given ControlTemplate property to a property of the control that the ControlTemplate is applied to. To well understand the issue here is an example bellow

<Window.Resources>
    <ControlTemplate x:Key="template">
            <Canvas>
                <Canvas.RenderTransform>
                    <RotateTransform Angle="20"/>
                    </Canvas.RenderTransform>
                <Ellipse Height="100" Width="150"
                    
 Fill="{Binding
               
 RelativeSource={RelativeSource TemplatedParent},
                Path=Background}">

                  </Ellipse>
                <ContentPresenter Margin="35"
                      Content
="{Binding RelativeSource={RelativeSource 
                      TemplatedParent
},Path=Content}"/>
            </Canvas>
        </ControlTemplate>
    </Window.Resources>
        <Canvas Name="Parent0">
        <Button   Margin="50"
                  Template
="{StaticResource template}" Height="0"
                  Canvas.Left
="0" Canvas.Top="0" Width="0">
            <TextBlock FontSize="22">Click me</TextBlock>
        </Button>
    </Canvas>

If I want to apply the properties of a given control to its control template then I can use the TemplatedParent mode. There is also a similar one to this markup extension which is the TemplateBinding which is a kind of short hand of the first one, but the TemplateBinding is evaluated at compile time at the contrast of the TemplatedParent which is evaluated just after the first run time. As you can remark in the bellow figure, the background and the content are applied from within the button to the control template.



4. PreviousData

This is the most ambiguous and the less used mode of the RelativeSource, I mean the PreviousData one. The PreviousData is used for particular cases. Its purpose is to tie the given property to another property with a particular assignment; I mean it assigns the previous value of the property to the bound one. In other word, if you have a TextBox that has a text property and another control that has a value property that holds data. Say that value is actually 5 and it was 3 just before. The 3 is assigned to the text property of the TextBox and not 5. This leads to the idea that this kind of RelativeSource is frequently used with the items controls.

To understand the phenomenon of the RelativeSource let's expose this sample. I will add an ItemsControl into the scene and I will aliment it from a custom collection

<Grid>
    <ItemsControl></ItemsControl>
</
Grid>

This ItemsControl is alimented using this collection:

public class Items : ObservableCollection<Item>
    {
        
public Items()
        {
            Add(
new Item { Value = 80.23 });
            Add(
new Item { Value = 126.17 });
            Add(
new Item { Value = 130.21 });
            Add(
new Item { Value = 115.28 });
            Add(
new Item { Value = 131.21 });
            Add(
new Item { Value = 135.22 });
            Add(
new Item { Value = 120.27 });
            Add(
new Item { Value = 110.25 });
            Add(
new Item { Value = 90.20 });
        }
    }


It's an ObservableCollection of type Item that I had developed and that holds a simple property which is called Value, it is of type double.

 
public class Item :INotifyPropertyChanged
    {
        
private double _value;

        public double Value
        {
            
get { return _value; }
            
set { _value = value; OnPropertyChanged("Value"); }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

     protected void OnPropertyChanged(string PropertyName)
     {
       
if (null != PropertyChanged)
       {
         PropertyChanged(
this,
              
new PropertyChangedEventArgs(PropertyName));
       }
    }
 }

Now, to bind the 
ItemsControl to the collection data, I will set the DataContext property of the whole window to the collection at the Window constructor level.

 public Window1()

        {

            InitializeComponent();

            this.DataContext = new Items();

        }

And then I'll specify the binding of the ItemsControl

ItemsControl ItemsSource="{Binding}" Margin="10"

Then the result will be like this. I mean not presentable.



Therefore, we have to apply some features to enhance the visual of that representation.

<ItemsControl ItemsSource="{Binding}" Margin="10">

        <ItemsControl.ItemsPanel>
           <ItemsPanelTemplate>
              <StackPanel Orientation="Horizontal"/>
           </ItemsPanelTemplate>
           </ItemsControl.ItemsPanel>
       <ItemsControl.ItemTemplate>
          <DataTemplate>
              <StackPanel>
             <Border CornerRadius="3" BorderThickness="3"
                Width
="80" Height="{Binding Value}"
                            Margin
="0,0,35,0" 
                           
 BorderBrush="Violet" 
                            Background
="BlueViolet">
                            <TextBlock Text="{Binding Value}"
                               FontWeight
="bold"
                               VerticalAlignment
="Center"
                               
HorizontalAlignment="Center" 
                               Foreground
="Wheat">
                        <TextBlock.RenderTransform>
                  <TransformGroup>
                            <ScaleTransform ScaleY="-1"/>
                 
</TransformGroup>
                  </TextBlock.RenderTransform>
                  </TextBlock>
             </Border>
         </StackPanel>
      </DataTemplate>
   </ItemsControl.ItemTemplate>
   <
ItemsControl.RenderTransform>
       <TransformGroup>
         <ScaleTransform ScaleY="-1"/>
         <TranslateTransform Y="250"/>
       </TransformGroup>
   </ItemsControl.RenderTransform>
 </ItemsControl>


Shortly, I will describe the above XAML. First, the ItemsPanel will arrange the items within an horizontal StackPanel for more information about the ItemsPanel please refer to this link

http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemspanel.aspx


Second, the DataTemplate is used to present the data as a border; the border height is bound to the Value of the item class to reflect the Values that the collection holds. The same border includes a TextBlock that displays the Value of the Item object.

For more information about te DateTemplate please refer to this MSDN link

http://msdn.microsoft.com/en-us/library/system.windows.datatemplate.aspx



The RenderTransform is used to emphasize the position of the items in the scene. For more information about transformation please refer to this MSDN link

http://msdn.microsoft.com/en-us/library/system.windows.media.transform.aspx


The result of the presentation will be as follow



Now, the main purpose of this demo is to show the characteristic of the RelativeSource.PreviousData mode.

The idea consists of adding a TextBox and tie the Text property to the Value of the previous border in the items' list. Something that seems to be as the bellow representation



As you can note, each TextBlock represents the previous value that the previous item holds. This is in fact the magic of the PreviousData of the RelativeSource mode.

The idea is to add the TextBlock to the DataTemplate as Follow

<TextBlock FontSize="14" FontWeight="bold" Margin="20"
            
Text="{Binding RelativeSource={RelativeSource PreviousData},
                   Path
=Value}">
                          <TextBlock.RenderTransform>
                              <ScaleTransform ScaleY="-1"/>
                          </TextBlock.RenderTransform>   
</TextBlock>

Then the whole picture will be

<Grid>
    <ItemsControl ItemsSource="{Binding}" Margin="10">
        <ItemsControl.RenderTransform>
            <TransformGroup>
                <ScaleTransform ScaleY="-1"/>
                <TranslateTransform Y="250"/>
            </TransformGroup>
        </ItemsControl.RenderTransform>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock FontSize="14" FontWeight="bold"
                           Margin
="20"
                           
Text="{Binding
                           RelativeSource
={RelativeSource PreviousData},
                                               Path
=Value}">
                      <TextBlock.RenderTransform>
                          <ScaleTransform ScaleY="-1"/>
                      </TextBlock.RenderTransform>   
                    
</TextBlock>
                    <Border CornerRadius="3" BorderThickness="3"
                           Width
="80" Height="{Binding Value}"
                           Margin
="0,0,35,0" 
                       
 BorderBrush="Violet" Background="BlueViolet">
                        <TextBlock Text="{Binding Value}"
                           FontWeight
="bold" VerticalAlignment="Center"
                                  
 HorizontalAlignment="Center"
                                   Foreground
="Wheat">
                        <TextBlock.RenderTransform>
                            <TransformGroup>
                        <ScaleTransform ScaleY="-1"/>
                        
</TransformGroup>
                        </TextBlock.RenderTransform>
                        </TextBlock>
                    </Border>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</
Grid>

Of Course, we could do more that this using RelativeSource.PreviousData but this will be enough to have a general consistent idea about that mode. That's all

728x90
Posted by kjun.kr
,