سؤال

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

لدي نموذج بيانات شخص بسيط:

class Person : INotifyPropertyChanged
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

يبدو تعبير الربط الخاص بي كما يلي:

<TextBlock Text="{Binding Converter={StaticResource personNameConverter}" />

يبدو المحول الخاص بي كما يلي:

class PersonNameConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Person p = value as Person;
        return p.FirstName + " " + p.LastName;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

إذا قمت بربط البيانات بدون محول فسيعمل بشكل رائع:

<TextBlock Text="{Binding Path=FirstName}" />
<TextBlock Text="{Binding Path=LastName}" />

ماذا ينقصني؟

يحرر:فقط لتوضيح بعض الأمور، كل من Joel وAlan على صواب فيما يتعلق بواجهة INotifyPropertyChanged التي يجب تنفيذها.في الواقع، لقد قمت بتطبيقه بالفعل ولكنه لا يزال لا يعمل.

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

أخيرًا، إنه خيار لإضافة خاصية مركبة "FullName" والربط بها، لكنني ما زلت أتساءل لماذا لا يحدث التحديث عندما يستخدم الربط محولًا.حتى عندما أضع نقطة فاصل في كود المحول، فإن مصحح الأخطاء لا يصل إلى هناك عند إجراء تحديث للبيانات الأساسية :-(

شكرا ، أوري

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

المحلول

(انظر التعديلات أدناه؛أحدث:#2)

لا يتم تحديثه لأن لديك Person الكائن غير قادر على إخطار أي شيء بقيمة FirstName أو LastName تغير. انظر هذا السؤال.

وإليك كيفية التنفيذ INotifyPropertyChanged. (تم التحديث، راجع تحرير 2)

using System.ComponentModel;

class Person : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;

    string _firstname;
    public string FirstName {
        get {
            return _firstname;
        }
        set {
            _firstname = value;
            onPropertyChanged( "FirstName", "FullName" );
        }
    }

    string _lastname;
    public string LastName {
        get {
            return _lastname;
        }
        set {
            _lastname = value;
            onPropertyChanged( "LastName", "FullName" );
        }
    }

    public string FullName {
        get {
            return _firstname + " " + _lastname;
        }
    }

    void onPropertyChanged( params string[] propertyNames ) {
        PropertyChangedEventHandler handler = PropertyChanged;

        if ( handler != null ) {
            foreach ( var pn in propertyNames ) {
                handler( this, new PropertyChangedEventArgs( pn ) );
            }
        }
    }
}

تحرير 1

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

تحرير 2

لقد اكتشفت ذلك.لا يتم إعلامك بأن الخصائص قد تم تحديثها لأنها مرتبطة بالكائن نفسه، وليس إحدى تلك الخصائص.حتى عندما فعلت Person أ DependencyObject و مصنوع FirstName و LastName DependencyProperties, ، لن يتم تحديثه.

أنت سوف يجب أن تستخدم أ FullName الملكية، ولقد قمت بتحديث رمز Person الطبقة أعلاه لتعكس ذلك.ثم يمكنك ربط Title. (ملحوظة: لقد قمت بتعيين Person كائن مثل WindowDataContext.)

Title="{Binding Path=FullName, Mode=OneWay}"

إذا كنت تقوم بتحرير الأسماء في ملف TextBox وأريد أن ينعكس الاسم على الفور بدلاً من ظهوره TextBox يفقد التركيز، يمكنك القيام بذلك:

<TextBox Name="FirstNameEdit"
    Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged}" />

أعلم أنك لا تريد استخدام ملف FullName الممتلكات، ولكن أي شيء من شأنه أن يحقق ما تريده من المحتمل أن يكون قليلاً من جهاز Rube Goldberg.مثل التنفيذ INotifyPropertyChanged و أ Person الملكية على Window الطبقة نفسها، مع وجود Window استمع على PropertyChanged الحدث من أجل إطلاق النار WindowPropertyChanged الحدث، واستخدام الربط النسبي مثل ما يلي.كنت قد قمت أيضًا بتعيين Person الملكية من قبل InitializeComponent() أو النار PropertyChanged بعد ضبط Person الملكية بحيث تظهر، بطبيعة الحال.(وإلا سيكون null خلال InitializeComponent() ويحتاج إلى معرفة متى يكون Person.)

<Window.Resources>
    <loc:PersonNameConverter
        x:Key="conv" />
</Window.Resources>
<Window.Title>
    <Binding
        RelativeSource="{RelativeSource Self}"
        Converter="{StaticResource conv}"
        Path="Person"
        Mode="OneWay" />
</Window.Title>

نصائح أخرى

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

<MultiBinding Converter="{IMultiValueConverter goes here..}">
    <Binding />
    <Binding Path="FirstName" />
    <Binding Path="LastName" />
</MultiBinding>

وأو إذا كنت تستخدم سوى الاسم الأول واسم العائلة، تجريد الكائن شخص من الربط إلى شيء من هذا القبيل:

<MultiBinding Converter="{IMultiValueConverter goes here..}">
    <Binding Path="FirstName" />
    <Binding Path="LastName" />
</MultiBinding>

وعلى MultiValueConverter يبدو مثل هذا:

class PersonNameConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
            return values[0].ToString() + " " + values[1].ToString();
    }

    public object ConvertBack(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
            throw new NotImplementedException();
    }
}

ولكن بطبيعة الحال، فإن الجواب مختارة تعمل أيضا، ولكن MultiBinding يعمل أكثر أناقة ...

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

using System.ComponentModel;

namespace INotifyPropertyChangeSample
{
    public class Person : INotifyPropertyChanged
    {
        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set
            {
                if (firstName != value)
                {
                    firstName = value;
                    OnPropertyChanged("FirstName");
                    OnPropertyChanged("FullName");
                }
            }
        }

        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set
            {
                if (lastName != value)
                {
                    lastName = value;
                    OnPropertyChanged("LastName");
                    OnPropertyChanged("FullName");
                }
            }
        }

        public string FullName
        {
            get { return firstName + " " + lastName; }
        } 

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

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

        #endregion
    }
}

ولديك التجليد والآن تبدو مثل هذا:

<TextBlock Text="{Binding Person.FullName}" />

وأنا لم تحقق ذلك، ولكن يمكنك أيضا أن تجرب التالي

<TextBlock Text="{Binding Path=/, Converter={StaticResource personNameConverter}}" />
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top