WPF: значително ли е обвързването на данни за модалните диалози?

(Аз съм доста нов в WPF, така че този въпрос може да изглежда очевиден или непоследователен.)

Има изискване за редактиране на част от основните бизнес данни на приложението от дъщерен модален прозорец и за актуализиране на данните само ако потребителят натисне бутона OK в този прозорец. Нека наречем този прозорец SettingsDialog.

В този случай все още ли е разумно да се използва обвързване на WPF данни за обвързване на контролите на SettingsDialog с бизнес данни? (И ако е така, как да актуализирате бизнес данни само когато потребителят натисне бутона OK на SettingsDialog?)

Или е по-добре ръчно да присвоите стойностите на контролите на SettingsDialog от бизнес данни, докато SettingsDialog се показва, и след това да ги присвоите обратно само ако потребителят натисне бутона OK?

Какви са аргументите за правилен избор (по-малък или по-ясен код, производителност, разширяемост)?

Има ли някакъв признат модел на проектиране за подобни случаи?

РЕДАКТИРАНЕ: Отбелязах отговора на Bubblewrap като приет, защото пасва най-много на моя конкретен случай. Въпреки това отговорите на Гард и Джон също изглеждат приемливи.

За да обобщим: използването на обвързване на данни има някои предимства. Той позволява на SettingsDialog да не знае нищо за вътрешните връзки и зависимости на бизнес обекта (ако има такива), позволява лесно превключване от модален към немодален режим, намалява зависимостите между GUI и бизнес данни.

За да се приложи промяна на обект при щракване върху бутона OK, може да се използва клониране/присвояване на обект или обектът може да реализира интерфейс IEditableObject.

В някои тривиални случаи обаче използването на обвързване на данни може да доведе до някои ненужни разходи.


person Alex Che    schedule 10.08.2009    source източник


Отговори (5)


Срещал съм подобни изисквания преди и предпочитам два варианта, като и двата използват обвързване на данни.

В първия вариант клонираме обекта и се свързваме с клонинга. Когато потребителят натисне OK, клонираният обект се заменя с реален обект. Това е най-вече полезно, когато редактирате един обект наведнъж и обектът не се препраща директно от други обекти. Ако има препратка, тогава вместо това можете да копирате стойностите от вашия клонинг към вашия оригинал, така че препратките да останат непокътнати. Това спестява работа, тъй като не е необходима допълнителна работа в редакторите, най-много трябва да дефинирате 2 метода на вашия обект, Clone и възможно Apply метод за копиране на стойностите от клонинга.

Вторият вариант свързваме с оригиналния обект, но съхраняваме оригиналните стойности в нашия диалогов прозорец за редактиране или в локални полета, или във временен обект с данни. Когато потребителят натисне OK, не трябва да се случва нищо специално, но когато потребителят натисне Отказ, връщаме стойностите. Това е най-вече полезно, когато редактирате само няколко прости свойства в диалоговия прозорец.

Предпочитам решения за обвързване на данни, тъй като не замърсява диалоговите прозорци за редактиране с логиката за прилагане/отмяна, която, ако е внедрена в редакторите, зависи много от вашия XAML: контролите могат да бъдат преименувани, полето с падащ списък може да бъде заменено с текстово поле и т.н., всичко това би повлияло код, който ръчно събира данни от контролите.

Методите Clone/Apply трябва да са само във вашите бизнес обекти, които на теория са по-стабилни от вашите UI редактори. Дори ако XAML се промени, в повечето случаи вашите обвързвания могат да останат същите. Например промяна от комбинирано поле към текстово поле означава само, че се свързвате с Text вместо SelectedValue, но действителното свързване е същото.

person Bubblewrap    schedule 10.08.2009

Една от възможностите е вашият бизнес обект да внедри IEditableObject .

  • Преди да покажете прозореца, извикайте BeginEdit на вашия обект
  • Ако потребителят щракне върху OK, извикайте EndEdit
  • Ако потребителят щракне върху Отказ, извикайте CancelEdit

IEditableObject обикновено се използва в сценарии за мрежа от данни, но работи добре и за описания от вас случай. Плюс това, ако потребителският ви интерфейс някога се промени, за да позволи вградено редактиране в DataGrid, няма да се налага да променяте своя бизнес обект.

person John Myczek    schedule 10.08.2009
comment
Благодаря ти! Поех по пътя на IEditableObject и не съжалявам. - person Andrei Rînea; 17.05.2010

Можете да използвате обвързване на данни за еднопосочно актуализиране на GUI, но ако искате да отложите актуализирането на бизнес модела само след натискане на OK, по-добре е да направите това в код. Обвързването на данни изобщо в този случай може да е ненужно.

Вземете например FolderBrowserDialog. Можете да зададете първоначална стойност на SelectedPath, преди да извикате ShowDialog(), но изчаквате диалоговият прозорец да се върне с DialogResult.OK, преди да обработите данните. Подобен подход трябва да работи за вашата ситуация.

person Will Eddins    schedule 10.08.2009

На манипулатор за зареждане на формуляр Преминете към колекцията Bindings и задайте всеки DataSourceUpdateMode на Никога. На OK манипулатор, зададен на противоположния (DataSourceUpdateMode.OnValidation) и формуляр за извикване ValidateChildren

ако обвързването се извършва чрез GUI, ще има нов член на формуляра на класа BindingSource. Това опростява малко - обвързванията, достъпни от CurrencyManager.Bindings, и BindingSource.EndEdit могат да се използват вместо ValidateChildren.

person vlad    schedule 18.11.2009

Наскоро срещнах същия проблем - имам изскачащи диалогови форми за редактиране на свойствата на обекта. След известно коригиране стигнах до кода по-долу, който изглежда работи добре. Дано помогне на някого.

Използвам това, за да покажа изскачащия формуляр

    public static bool EditTask(MyTask task)
    {
        //make a backup object in case user clicks Cancel
        MyTask backupTask = new MyTask();
        CloneObject(task, backupTask);

        //dialog form uses data binding to the backupTask object
        MyTaskWindow f = new MyTaskWindow(backupTask);

        f.ShowDialog();
        if (f.DialogResult.HasValue && f.DialogResult.Value)
        {
            //user clicked "Ok" - clone everything back
            CloneObject(backupTask, task);
            return true;
        }
        else
        {                
            return false;
        }
    }

    //Reflection is used to clone object
    //copied from http://www.c-sharpcorner.com/UploadFile/ff2f08/deep-copy-of-object-in-C-Sharp/
    private static void CloneObject(object objSource, object objTarget)
    {
        //step : 1 Get the type of source object and create a new instance of that type
        Type typeSource = objSource.GetType();
        //object objTarget = Activator.CreateInstance(typeSource);

        //Step2 : Get all the properties of source object type
        PropertyInfo[] propertyInfo = typeSource.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

        //Step : 3 Assign all source property to taget object 's properties
        foreach (PropertyInfo property in propertyInfo)
        {
            //Check whether property can be written to
            if (property.CanWrite)
            {
                //Step : 4 check whether property type is value type, enum or string type
                if (property.PropertyType.IsValueType || property.PropertyType.IsEnum || property.PropertyType.Equals(typeof(System.String)))
                {
                    property.SetValue(objTarget, property.GetValue(objSource, null), null);
                }
                //else property type is object/complex types, so need to recursively call this method until the end of the tree is reached
                else
                {
                    object objPropertyValue = property.GetValue(objSource, null);
                    if (objPropertyValue == null)
                    {
                        property.SetValue(objTarget, null, null);
                    }
                    else
                    {
                        Type newTypeSource = objPropertyValue.GetType();
                        object newObjTarget = Activator.CreateInstance(newTypeSource);
                        CloneObject(objPropertyValue, newObjTarget);
                        property.SetValue(objTarget, newObjTarget, null);
                    }
                }
            }
        }            
    }
person Vadim    schedule 22.11.2013