سؤال

كيف أستخدم RelativeSource مع روابط WPF وما هي حالات الاستخدام المختلفة؟

هل كانت مفيدة؟

المحلول

إذا كنت تريد الارتباط بخاصية أخرى على الكائن:

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

إذا كنت ترغب في الحصول على عقار على أحد الأجداد:

{Binding Path=PathToProperty,
    RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}

إذا كنت ترغب في الحصول على خاصية على الأصل القالب (حتى تتمكن من إجراء عمليات ربط ثنائية الاتجاه في ControlTemplate)

{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}

أو أقصر (هذا يعمل فقط مع روابط OneWay):

{TemplateBinding Path=PathToProperty}

نصائح أخرى

Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

السمة الافتراضية ل RelativeSource هل Mode ملكية.يتم تقديم مجموعة كاملة من القيم الصالحة هنا (من MSDN):

  • البيانات السابقة يسمح لك بربط عنصر البيانات السابق (وليس عنصر التحكم الذي يحتوي على عنصر البيانات) في قائمة عناصر البيانات التي يتم عرضها.

  • TemplatedParent يشير إلى العنصر الذي يتم تطبيق القالب عليه (الذي يوجد فيه العنصر المرتبط بالبيانات).يشبه هذا إعداد TemplateBindingExtension ولا ينطبق إلا إذا كان الربط داخل قالب.

  • الذات يشير إلى العنصر الذي تقوم بتعيين الارتباط عليه ويسمح لك بربط خاصية واحدة لذلك العنصر بخاصية أخرى على نفس العنصر.

  • FindAncestor يشير إلى السلف في السلسلة الأصلية للعنصر المرتبط بالبيانات.يمكنك استخدام هذا للربط بسلف نوع معين أو فئاته الفرعية.هذا هو الوضع الذي تستخدمه إذا كنت تريد تحديد AncestorType و/أو AncestorLevel.

فيما يلي شرح أكثر وضوحًا في سياق بنية MVVM:

enter image description here

تخيل هذه الحالة، مستطيل نريد أن يكون ارتفاعه دائمًا مساويًا لعرضه، مربعًا على سبيل المثال.يمكننا القيام بذلك باستخدام اسم العنصر

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

ولكن في هذه الحالة أعلاه نحن ملزمون بالإشارة إلى اسم الكائن المرتبط، وهو المستطيل.يمكننا الوصول إلى نفس الغرض بشكل مختلف باستخدام RelativeSource

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

في هذه الحالة لسنا ملزمين بذكر اسم الكائن المرتبط وسيكون العرض دائمًا مساويًا للارتفاع كلما تغير الارتفاع.

إذا كنت تريد أن يكون العرض هو نصف الارتفاع، فيمكنك القيام بذلك عن طريق إضافة محول إلى ملحق علامة Binding.لنتخيل حالة أخرى الآن:

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

يتم استخدام الحالة المذكورة أعلاه لربط خاصية معينة لعنصر معين بأحد عناصره الأصلية المباشرة حيث أن هذا العنصر يحمل خاصية تسمى الأصل.يقودنا هذا إلى وضع مصدر نسبي آخر وهو FindAncestor.

يعرض بشير بجاوي حالات استخدام RelativeSources في WPF في مقالته هنا:

The RististerSource هو امتداد ترميز يتم استخدامه في حالات ربط معينة عندما نحاول ربط خاصية لكائن معين إلى خاصية أخرى للكائن نفسه ، عندما نحاول ربط خاصية كائن إلى أحد آبائها النسبيين ، عند ربط قيمة خاصية التبعية لقطعة من XAML في حالة تطوير التحكم المخصص وأخيراً في حالة استخدام تفاضلية لسلسلة من البيانات المرتبطة.يتم التعبير عن كل هذه المواقف كطرق المصدر النسبية.وسوف أكشف كل تلك الحالات واحدة تلو الأخرى.

  1. الوضع الذاتي:

تخيل هذه الحالة ، وهو مستطيل نريد أن يكون ارتفاعه دائمًا مساوياً لعرضه ، ودعنا مربعًا.يمكننا القيام بذلك باستخدام اسم العنصر

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

ولكن في هذه الحالة أعلاه ، نحن ملزمون بالإشارة إلى اسم الكائن الملزم ، أي المستطيل.يمكننا الوصول إلى نفس الغرض بشكل مختلف باستخدام الأقارب

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

في هذه الحالة ، لسنا ملزمين بذكر اسم الكائن الملزم وسيكون العرض دائمًا مساوياً للارتفاع كلما تم تغيير الارتفاع.

إذا كنت ترغب في معلمة العرض ليكون نصف الارتفاع ، فيمكنك القيام بذلك عن طريق إضافة محول إلى امتداد ترميز الربط.لنتخيل حالة أخرى الآن:

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

يتم استخدام الحالة المذكورة أعلاه لربط خاصية معينة بعنصر معين بأحد الوالدين المباشرين لأن هذا العنصر يحمل خاصية تسمى الوالدين.هذا يقودنا إلى وضع مصدر نسبي آخر وهو FindanCestor واحد.

  1. وضع FindAncestor

في هذه الحالة ، سيتم ربط خاصية لعنصر معين بأحد والديه ، من Corse.الفرق الرئيسي في الحالة أعلاه هو حقيقة أن الأمر متروك لك لتحديد نوع الأجداد ورتبة الجد في التسلسل الهرمي لربط العقار.بالمناسبة حاول اللعب مع هذه القطعة من 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>

الموقف أعلاه هو من عنصرين نصين من نص مضمّن داخل سلسلة من الحدود وعناصر القماش التي تمثل آباءهم الهرميين.ستعرض TextBlock الثاني اسم الوالد المحدد على مستوى المصدر النسبي.

لذا حاول تغيير الأجداد = 2 إلى Ancestorlevel = 1 ومعرفة ما يحدث.ثم حاول تغيير نوع الجد من AncestorType = الحدود إلى Canstortype = قماش ومعرفة ما يحدث.

سوف يتغير النص المعروض وفقًا لنوع السلف والمستوى.ثم ماذا يحدث إذا لم يكن مستوى الأجداد مناسبًا لنوع الأجداد؟هذا سؤال جيد ، وأنا أعلم أنك على وشك طرحها.لن يتم طرح الاستجابة أي استثناءات وسيتم عرض nothings على مستوى TextBlock.

  1. TemplatedParent

يتيح هذا الوضع ربط خاصية ControlTemplate معينة إلى خاصية عنصر التحكم الذي يتم تطبيق ControlTemplate.لفهم القضية هنا هو مثال

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

إذا كنت أرغب في تطبيق خصائص عنصر تحكم معين على قالب التحكم الخاص به ، فيمكنني استخدام وضع templatedparent.هناك أيضًا تمديد مشابه لتمديد الترميز هذا وهو التداخل الذي يعد نوعًا من اليد القصيرة للواحد الأول ، ولكن يتم تقييم التسلل في وقت الترجمة على تناقض templatedparent الذي يتم تقييمه بعد وقت التشغيل الأول مباشرة.كما يمكنك الملاحظة في الشكل المرفق ، يتم تطبيق الخلفية والمحتوى من داخل الزر إلى قالب التحكم.

في دبليو بي إف RelativeSource ملزمة يعرض ثلاثة properties لتعيين:

1.وضع: هذا enum يمكن أن يكون لها أربع قيم:

أ.البيانات السابقة(value=0): يقوم بتعيين القيمة السابقة لـ property إلى واحد

ب.تيمبليتيدبرينت(value=1): يتم استخدامه عند تحديد templates من أي عنصر تحكم ويريد ربط قيمة/خاصية control.

على سبيل المثال، يُعرِّف ControlTemplate:

  <ControlTemplate>
        <CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
 </ControlTemplate>

ج.الذات(value=2): عندما نريد الارتباط من a self أو أ property الذات.

على سبيل المثال: إرسال حالة التحقق من checkbox مثل CommandParameter أثناء ضبط Command على CheckBox

<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />

د.فيندانسستور(value=3): عندما تريد ربط من أحد الوالدين controlفي Visual Tree.

على سبيل المثال: ربط أ checkbox في records اذا كان grid،لو header checkbox مفحوص

<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />

2.نوع السلف: عندما يكون الوضع FindAncestor ثم حدد نوع الجد

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}

3.مستوى الأجداد: عندما يكون الوضع FindAncestor ثم ما هو مستوى السلف (إذا كان هناك نوعان من نفس النوع من الوالدين في visual tree)

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}

أعلاه جميع حالات الاستخدام لـ RelativeSource binding.

هنا رابط مرجعي.

لا تنسى TemplatedParent:

<Binding RelativeSource="{RelativeSource TemplatedParent}"/>

أو

{Binding RelativeSource={RelativeSource TemplatedParent}}

تجدر الإشارة إلى أنه بالنسبة لأولئك الذين يتعثرون في هذا التفكير في Silverlight:

يقدم Silverlight مجموعة فرعية مخفضة فقط من هذه الأوامر

لقد قمت بإنشاء مكتبة لتبسيط بناء جملة الربط لـ WPF بما في ذلك تسهيل استخدام RelativeSource.وهنا بعض الأمثلة.قبل:

{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
{Binding Path=Text, ElementName=MyTextBox}

بعد:

{BindTo PathToProperty}
{BindTo Ancestor.typeOfAncestor.PathToProperty}
{BindTo Template.PathToProperty}
{BindTo #MyTextBox.Text}

فيما يلي مثال لكيفية تبسيط ربط الطريقة.قبل:

// C# code
private ICommand _saveCommand;
public ICommand SaveCommand {
 get {
  if (_saveCommand == null) {
   _saveCommand = new RelayCommand(x => this.SaveObject());
  }
  return _saveCommand;
 }
}

private void SaveObject() {
 // do something
}

// XAML
{Binding Path=SaveCommand}

بعد:

// C# code
private void SaveObject() {
 // do something
}

// XAML
{BindTo SaveObject()}

يمكنك العثور على المكتبة هنا: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html

لاحظ في المثال "قبل" الذي أستخدمه لربط الطريقة أن هذا الرمز قد تم تحسينه بالفعل باستخدام RelayCommand آخر ما قمت بفحصه ليس جزءًا أصليًا من WPF.وبدون ذلك لكان المثال "قبل" أطول.

بعض القطع والقطع المفيدة:

إليك كيفية القيام بذلك في الغالب في التعليمات البرمجية:

Binding b = new Binding();
b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1);
b.Path = new PropertyPath("MyElementThatNeedsBinding");
MyLabel.SetBinding(ContentProperty, b);

لقد قمت بنسخ هذا إلى حد كبير من ربط المصدر النسبي في الكود الخلفي.

كما أن صفحة MSDN جيدة جدًا فيما يتعلق بالأمثلة: فئة المصدر النسبي

لقد نشرت للتو حل آخر للوصول إلى DataContext للعنصر الأصلي في Silverlight الذي يناسبني.يستخدم Binding ElementName.

لم أقرأ كل إجابة، ولكني أريد فقط إضافة هذه المعلومات في حالة ربط أمر المصدر النسبي للزر.

عند استخدام مصدر نسبي مع Mode=FindAncestor, ، يجب أن يكون الربط مثل:

Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"

إذا لم تقم بإضافة DataContext في المسار الخاص بك، فلن يتمكن من استرداد الخاصية في وقت التنفيذ.

هذا مثال على استخدام هذا النمط الذي نجح معي على شبكات البيانات الفارغة.

<Style.Triggers>
    <DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}" Value="0">
        <Setter Property="Background">
            <Setter.Value>
                <VisualBrush Stretch="None">
                    <VisualBrush.Visual>
                        <TextBlock Text="We did't find any matching records for your search..." FontSize="16" FontWeight="SemiBold" Foreground="LightCoral"/>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Setter.Value>
        </Setter>
    </DataTrigger>
</Style.Triggers>

إذا لم يكن العنصر جزءًا من الشجرة المرئية، فلن يعمل RelativeSource أبدًا.

في هذه الحالة، عليك تجربة أسلوب مختلف، ابتكره توماس ليفيسك.

لديه الحل على مدونته تحت [WPF] كيفية ربط البيانات عندما لا يتم توريث DataContext.وهو يعمل ببراعة تامة!

وفي حالة تعطل مدونته، وهو أمر غير محتمل، يحتوي الملحق أ على نسخة طبق الأصل من مقالته.

من فضلك لا تعليق هنا، من فضلك قم بالتعليق مباشرة على منشور مدونته.

الملحق أ:مرآة مشاركة المدونة

تعد خاصية DataContext في WPF مفيدة للغاية، لأنها ترث تلقائيًا من قبل جميع العناصر الفرعية للعنصر الذي قمت بتعيينه فيه؛لذلك لا تحتاج إلى ضبطه مرة أخرى على كل عنصر تريد ربطه.ومع ذلك، في بعض الحالات لا يمكن الوصول إلى DataContext:يحدث ذلك للعناصر التي لا تشكل جزءًا من الشجرة المرئية أو المنطقية.قد يكون من الصعب جدًا ربط خاصية بهذه العناصر ...

ولنوضح ذلك بمثال بسيط:نريد عرض قائمة المنتجات في DataGrid.في الشبكة، نريد أن نكون قادرين على إظهار عمود السعر أو إخفائه، استنادًا إلى قيمة خاصية ShowPrice التي يعرضها ViewModel.الطريقة الواضحة هي ربط رؤية العمود بخاصية ShowPrice:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding ShowPrice,
                Converter={StaticResource visibilityConverter}}"/>

لسوء الحظ، تغيير قيمة ShowPrice ليس له أي تأثير، والعمود ظاهر دائمًا... لماذا؟إذا نظرنا إلى نافذة الإخراج في Visual Studio، نلاحظ السطر التالي:

خطأ في System.Windows.Data:2 :لا يمكن العثور على FrameworkElement أو FrameworkContentElement الحاكمين للعنصر الهدف.BindingExpression:Path=ShowPrice;DataItem=null;العنصر الهدف هو "DataGridTextColumn" (HashCode=32685253)؛الخاصية المستهدفة هي "الرؤية" (اكتب "الرؤية")

الرسالة غامضة إلى حد ما، ولكن المعنى بسيط جدًا في الواقع:لا يعرف WPF عنصر Framework الذي يجب استخدامه للحصول على DataContext، لأن العمود لا ينتمي إلى الشجرة المرئية أو المنطقية لـ DataGrid.

يمكننا محاولة تعديل الرابط للحصول على النتيجة المطلوبة، على سبيل المثال عن طريق تعيين RelativeSource إلى DataGrid نفسها:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding DataContext.ShowPrice,
                Converter={StaticResource visibilityConverter},
                RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>

أو يمكننا إضافة CheckBox مرتبط بـ ShowPrice، ومحاولة ربط رؤية العمود بالخاصية IsChecked عن طريق تحديد اسم العنصر:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding IsChecked,
                Converter={StaticResource visibilityConverter},
                ElementName=chkShowPrice}"/>

ولكن لا يبدو أن أيًا من هذه الحلول يعمل، فنحن نحصل دائمًا على نفس النتيجة ...

في هذه المرحلة، يبدو أن النهج الوحيد القابل للتطبيق هو تغيير رؤية العمود في الكود الخلفي، وهو ما نفضل عادةً تجنبه عند استخدام نمط MVVM... لكنني لن أستسلم قريبًا، على الأقل لا في حين أن هناك خيارات أخرى يجب مراعاتها 😉

الحل لمشكلتنا هو في الواقع بسيط للغاية، ويستفيد من فئة Freezable.الغرض الأساسي من هذه الفئة هو تعريف الكائنات التي لها حالة قابلة للتعديل وحالة للقراءة فقط، ولكن الميزة المثيرة للاهتمام في حالتنا هي أن الكائنات القابلة للتجميد يمكنها أن ترث DataContext حتى عندما لا تكون في الشجرة المرئية أو المنطقية.لا أعرف الآلية الدقيقة التي تمكن هذا السلوك، ولكننا سنستفيد منها لجعل عملنا ملزمًا ...

الفكرة هي إنشاء فئة (أسميتها BindingProxy لأسباب ستتضح قريبًا جدًا) ترث Freezable وتعلن عن خاصية تبعية البيانات:

public class BindingProxy : Freezable
{
    #region Overrides of Freezable

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    #endregion

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

يمكننا بعد ذلك الإعلان عن مثيل لهذه الفئة في موارد DataGrid، وربط خاصية البيانات بـ DataContext الحالي:

<DataGrid.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

الخطوة الأخيرة هي تحديد كائن BindingProxy (يمكن الوصول إليه بسهولة باستخدام StaticResource) كمصدر للربط:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding Data.ShowPrice,
                Converter={StaticResource visibilityConverter},
                Source={StaticResource proxy}}"/>

لاحظ أن مسار الربط قد تم بادئته بـ "Data"، نظرًا لأن المسار الآن مرتبط بكائن BindingProxy.

يعمل الربط الآن بشكل صحيح، ويتم إظهار العمود أو إخفاؤه بشكل صحيح استنادًا إلى خاصية ShowPrice.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top