Bài viết

Kho tài liệu và bài viết được chia sẻ, đánh giá bởi cộng đồng

RelativeSourses trong WPF

Cu Xin đã tạo 13:26 18-06-2021 Hoạt động 13:28 18-06-2021 620 lượt xem 3 bình luận

Nội dung bài viết

Bài viết được dịch từ: https://www.c-sharpcorner.com/UploadFile/yougerthen/relativesources-in-wpf/

 

Có nhiều bài viết nói về binding và source, và cách để bind các thuộc tính với nhau bằng cách sử dụng StaticResources, DynamicResources, mặc dù bạn có thể tìm thấy thông tin về RelativeResouce và các trường hợp ta cần sử dụng nó nhưng không đủ chi tiết ngay cả trong tài liệu của Microsoft. Trong bài viết này ta sẽ tìm hiểu các trường hợp sử dụng RelativeResource trong WPF.

 

RelativeSource là một markup extension được sử dụng trong các trường hợp binding cụ thể:

  • khi ta cố gắng bind một thuộc tính của một đối tượng nhất định với một thuộc tính khác của chính đối tượng đó.

  • khi ta cố bind một thuộc tính của một đối tượng tới một thuộc tính khác của parent của chúng.

  • khi ta binding một dependency property value tới một phần của XAML trong trường hợp phát triển custom control.

  • Cuối cùng là sử dụng a differential of a series of a bound data.

 

Tất cả các trường hợp đó đều được sử dụng ở relative source mode, tôi sẽ giải thích chi tiết.

Mode Self

Hãy tưởng tượng trường hợp này, một hình chữ nhật mà ta muốn thuộc tính height luôn bằng với thuộc tính width của nó, hay nói cách khác là một hình vuông. Ta có thể làm điều này bằng cách sử dụng element name.

<Rectangle Fill="Red" Name="rectangle"

                    Height="100" Stroke="Black"

                    Canvas.Top="100" Canvas.Left="100"

                    Width="{Binding ElementName=rectangle,

                    Path=Height}"/>

Nhưng trong trường hợp trên, ta buộc phải chỉ ra tên của đối tượng binding, cụ thể là rectangle. Nhưng ta có thể làm điều này bằng RelativeSource.

<Rectangle Fill="Red" Height="100"

                   Stroke="Black"

                   Width="{Binding RelativeSource={RelativeSource Self},

                   Path=Height}"/>

 

Đối với trường hợp trên, ta không bắt buộc phải chỉ rõ tên của đối tượng binding và chiều rộng sẽ luôn bằng chiều cao bất cứ khi nào chiều cao thay đổi.

Nếu bạn muốn làm cho chiều rộng bằng một nửa chiều cao thì bạn có thể làm điều này bằng cách thêm converter vào phần binding.

 

Giờ thì hãy thử tưởng tượng một trường hợp khác:

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

Trường hợp trên được sử dụng để liên kết một thuộc tính nhất định của một element nhất định với một trong những thuộc tính parent trực tiếp của nó, vì element có một thuộc tính được gọi là Parent.

Điều này dẫn ta đến một Mode khác là Mode FindAncestor.

Mode FindAncestor

Trong trường hợp này, một thuộc tính của một phần tử nhất định sẽ được binding tới một thuộc tính của parent của nó. Sự khác biệt chính với trường hợp trên là thực tế bạn phải xác định loại ancestor(tổ tiên) và cấp bậc của ancestor trong hệ thống phân cấp để binding thuộc tính. Nhân tiện hãy chạy thử đoạn XAML dưới đây.

<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>

Tình huống trên cho thấy hai TextBlock nằm trong một loạt các Border và Canvas element đại diện cho parent phân cấp của chúng. TextBlock thứ hai sẽ hiển thị tên của parent được chỉ định ở relative resource level.

 

Vì vậy hãy thử đổi AncestorLevel=2 thành AncestorLevel=1 và xem điều gì xảy ra. Sau đó hãy thử thay đổi type of ancestor AncestorType=Border thành AncestorType=Canvas và xem điều gì xảy ra.

 

Văn bản được hiển thị sẽ thay đổi theo Ancestor type và level. Nhưng điều gì sẽ xảy ra nếu ancestor level không đúng với ancestor type? Đó là một câu hỏi hay, và câu trả lời là không có exception nào được quăng ra và TextBlock sẽ chẳng hiển thị gì cả.

TemplatedParent

Chế độ này cho phép liên kết một thuộc tính của ControlTemplate đã cho với một thuộc tính của Control mà ControlTemplate đó được áp dụng. Ví dụ, bạn có một template muốn áp dụng nó cho một Button, bạn muốn đoạn text hiển thị trong template sẽ bằng với content có trong button.

 

Để hiểu rõ vấn đề này, hãy chạy ví dụ dưới đây.

<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>

Nếu bạn muốn áp dụng các thuộc tính của một control nhất định cho control template của nó thì bạn có thể sử dụng TemplatedMode. Ngoài ra còn có một markup extension tương tự là TemplateBinding, là một dạng viết tắt của cái trên, nhưng TemplateBinding được xác định tại compile time ngược với TemplatedParent được xác định chỉ sau lần chạy đầu tiên. Như bạn có thể thấy trong hình bên dưới, nền và nội dung được áp dụng từ bên trong Button vào control template.

 

WPFSource1.gif

PreviousData

Đây là mode không rõ ràng nhất và ít được sử dụng hơn các mode thuộc RelativeSource, PreviousData được sử dụng cho các trường hợp cụ thể. Mục đích của nó là để binding một thuộc tính tới một thuộc tính khác bằng một nhiệm vụ cụ thể; ý tôi là nó gán giá trị trước đó của thuộc tính cho giá trị được binding. Nói cách khác, nếu bạn có một TextBox có thuộc tính text và một control khác có lưu trữ dữ liệu. Giả sử giá trị đó thật ra là 5 mà trước đó là 3.  Thì 3 được gán cho thuộc tính text của TextBox chứ không phải 5. Điều này dẫn đến ý tưởng rằng kiểu relative source này thường được sử dụng với các items controls.

Để hiểu hiện tượng RelativeSource hãy khám phá ví dụ dưới đây. Ta sẽ thêm một ItemsControl vào khung ảnh và tôi sẽ biến nó thành một custom collection.


 

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

ItemsControl này được thay đổi dựa vào collection này.

Nó là một ObservableCollection thuộc type Item mà ta đã định nghĩa và nó chứa một thuộc tính duy nhất được gọi là Value kiểu 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));
       }

    }

}

Giờ, bind ItemsControl tới collection data, ta sẽ đặt DataContext của window này là collection ngay lúc ta khởi tạo Window.
 

public Window1()
     {
         InitializeComponent();
         this.DataContext = new Items();
     }

Và sau đó ta sẽ chỉ định binding cho ItemsControl

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

Kết quả sau khi chạy sẽ trông như sau. Ý tôi là nó không được trực quan lắm.

WPFSource2.gif

Do đó, ta phải áp dụng một số tính năng để nâng cấp tính trực quan của hình biểu diễn đó.

<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>

Tôi sẽ mô tả về XAML ở trên. Đầu tiên, ItemsPanel sẽ sắp xếp các item bên trong một StackPanel ngang để biết thêm thông tin chi tiết về các ItemsPanel.

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

Thứ hai, DataTemplate được sử dụng để thể hiện data như một border; chiều cao của border được binding tới Value của item class để tham chiếu các Values mà collection này có. Border tương tự bao gồm một TextBlock hiển thị Value của đối tượng Item.

Kết quả của đoạn code trên sẽ trông như sau.

WPFSource3.gif

Mục đích chính của demo này là show đặc tính của PreviousData Mode.

Ý tưởng bao gồm việc thêm một TextBox và binding thuộc tính Text với Value của border trước đó trong collection các item. Một cái gì đó trông như dưới đây.

WPFSource4.gif

Như bạn có thể thấy, mỗi TextBlock đại diện cho Value trước đó mà item nằm trước đó nắm giữ. Thực tế đây là một magic của PreviousData Mode.

Ý tưởng là thêm TextBlock vào DataTemplate dưới dạng Follow.

<TextBlock FontSize="14" FontWeight="bold" Margin="20"

            Text="{Binding RelativeSource={RelativeSource PreviousData},

                Path=Value}">

                          <TextBlock.RenderTransform>

                              <ScaleTransform ScaleY="-1"/>

                          </TextBlock.RenderTransform>   

</TextBlock>

 

Sau đó toàn bộ bức tranh sẽ là

<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>

Tất nhiên, ta có thể làm nhiều thứ hơn bằng cách sử dụng PreviousData nhưng điều này đủ để cho bạn nắm bắt được PreviousData mode.

Nội dung bài viết

Bình luận

Để bình luận, bạn cần đăng nhập bằng tài khoản Howkteam.

Đăng nhập
minhthan đã bình luận 20:42 04-08-2021

df

nicotin đã bình luận 23:08 21-06-2021

kkk

Câu hỏi mới nhất