пятница, 27 августа 2010 г.

Использование combobox в Silverlight 4. Часть 6

Теперь мы добавим в нашу форму с данными combobox со списком городов.

Еще раз напомню, что это можно сделать стандартными средствами Silverlight, использую свойство DataForm.Fields, однако такой способ практически не применим, в случае, когда количество полей в форме достаточно велико.

Вернемся к обработчику события генерации полей для формы:

<dataControls:DataForm CurrentItem="{Binding ElementName=customerDataGrid, Path=SelectedItem, Mode=TwoWay}"

AutoGenerateFields="True"

AutoCommit="False"

AutoEdit="False"

Width="400"

EditEnded="DataForm_EditEnded"

AutoGeneratingField="DataForm_AutoGeneratingField">

</dataControls:DataForm>

private void DataForm_AutoGeneratingField(object sender, DataFormAutoGeneratingFieldEventArgs e)

{

}

Там нужно найти свойство, отвечающее за поле с названием города и преобразовать его в ComboBox.

Свойство TownName нас больше не нужно, так что метаданные выглядят сейчас так:

namespace BusinessApplication.BLL

{

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.ComponentModel.DataAnnotations;

using System.Data.Objects.DataClasses;

using System.Linq;

using System.ServiceModel.DomainServices.Hosting;

using System.ServiceModel.DomainServices.Server;

// The MetadataTypeAttribute identifies CustomerMetadata as the class

// that carries additional metadata for the Customer class.

[MetadataTypeAttribute(typeof(Customer.CustomerMetadata))]

public partial class Customer

{

// This class allows you to attach custom attributes to properties

// of the Customer class.

//

// For example, the following marks the Xyz property as a

// required property and specifies the format for valid values:

// [Required]

// [RegularExpression("[A-Z][A-Za-z0-9]*")]

// [StringLength(32)]

// public string Xyz { get; set; }

internal sealed class CustomerMetadata

{

// Metadata classes are not meant to be instantiated.

private CustomerMetadata()

{

}

[Display(AutoGenerateField=false)]

public int ID { get; set; }

[Display(Order=1, Name="Customer Name")]

public string Name { get; set; }

[Include]

[Display(Order = 0, Name = "Customer Town")]

public Town Town { get; set; }

[Display(AutoGenerateField = false)]

public int TownID { get; set; }

}

}

// The MetadataTypeAttribute identifies TownMetadata as the class

// that carries additional metadata for the Town class.

[MetadataTypeAttribute(typeof(Town.TownMetadata))]

public partial class Town

{

// This class allows you to attach custom attributes to properties

// of the Town class.

//

// For example, the following marks the Xyz property as a

// required property and specifies the format for valid values:

// [Required]

// [RegularExpression("[A-Z][A-Za-z0-9]*")]

// [StringLength(32)]

// public string Xyz { get; set; }

internal sealed class TownMetadata

{

// Metadata classes are not meant to be instantiated.

private TownMetadata()

{

}

public EntityCollection<Customer> Customers { get; set; }

public int ID { get; set; }

public string Name { get; set; }

}

}

}

А Customer.shared.cs выглядит так:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace BusinessApplication.BLL

{

}

Перейдем к обработчику событий и изменим его следующщим образом:

private void DataForm_AutoGeneratingField(object sender, DataFormAutoGeneratingFieldEventArgs e)

{

if (e.PropertyName == "Town")

{

var ctx = (customerDomainDataSource.DomainContext as DomainService1);

var towns = ctx.Load(ctx.GetTownsQuery()).Entities;

Binding binding = new Binding("Town");

binding.Mode = BindingMode.TwoWay;

var townCombobox = new ComboBox();

townCombobox.ItemsSource = towns;

townCombobox.DisplayMemberPath = "Name";

townCombobox.SetBinding(ComboBox.SelectedItemProperty, binding);

e.Field.Content = townCombobox;

}

}

Этот код является первым приближением того, что мы хотим получить. Мы находим свойство с названием Town, создаем Combobox и заменяет текущее значение e.Field.Content (контрол для текущего свойства в форме с данными) на этот Сombobox.

Остается только связать его с данными. Свойство SelectedItem мы связываем со свойством Town нашего текущего контекста данных для формы (текущего кастомера). А свойсто Itemssource нам нужно связать со списком всех названий городов.

Как же это сделать? Мы можем получить их доменного сервиса при помощи запроса GetTowns. В сервисе он выглядит следующим образом:

public IQueryable<Town> GetTowns()

{

return this.ObjectContext.Towns;

}

А вот файле BusinessApplication1.Web.g.cs он выглядит так:

/// <summary>

/// Gets an EntityQuery instance that can be used to load <see cref="Town"/> entities using the 'GetTowns' query.

/// </summary>

/// <returns>An EntityQuery that can be loaded to retrieve <see cref="Town"/> entities.</returns>

public EntityQuery<Town> GetTownsQuery()

{

this.ValidateMethod("GetTownsQuery", null);

return base.CreateQuery<Town>("GetTowns", null, false, true);

}

То есть у него появилось новое название – GetTownsQuery.

Мы вызываем этот запрос и пробуем получить все сущности, возвращаемые этим запросом:

var ctx = (customerDomainDataSource.DomainContext as DomainService1);

var towns = ctx.Load(ctx.GetTownsQuery()).Entities;

Причем очень важно, что мы загружаем сущности в тот же dataSource, в котором хранится текущий кастомер. Если попробовать загрузить связанные сущности в другой dataSource, то при попытке изменить город, мы получим исключение при сохранении, которое говорит, как раз, о том, что сощности находятся в разных контейнерах сущностей.

Так или иначе, сейчас наш код не работает. Если тоставить breakpoint после строки var towns =… , то мы увидим, что набор towns пустой. Дело в том, что запросы с сервису выполняются асинхронно, и мы не может получить сущности сразу.

Более того, вызов

var towns = ctx.Load(ctx.GetTownsQuery()).Entities;

Может вернуть нам не совсем те записи, которые нам нужны.

Свойство Entities содержит все записи, которые уже были загружены ранее, то есть возможна ситуация, когда это свойство содержит больше данных, чем нам нужно в настоящий момент. Правда это все равно не относится к данному случаю.

Добавим код для callback метода, который вызовется, когда данные будут загружены.

private void customerDomainDataSource_LoadedData(object sender, System.Windows.Controls.LoadedDataEventArgs e)

{

if (e.HasError)

{

System.Windows.MessageBox.Show(e.Error.ToString(), "Load Error", System.Windows.MessageBoxButton.OK);

e.MarkErrorAsHandled();

}

}

private void DataForm_EditEnded(object sender, DataFormEditEndedEventArgs e)

{

if (e.EditAction == DataFormEditAction.Commit)

{

customerDomainDataSource.SubmitChanges();

}

}

private void DataForm_AutoGeneratingField(object sender, DataFormAutoGeneratingFieldEventArgs e)

{

if (e.PropertyName == "Town")

{

var ctx = (customerDomainDataSource.DomainContext as DomainService1);

var townsQ = ctx.Load(ctx.GetTownsQuery(), OnGetTowns, e.Field);

}

}

public void OnGetTowns(LoadOperation loadOperation)

{

Binding binding = new Binding("Town");

binding.Mode = BindingMode.TwoWay;

var townCombobox = new ComboBox();

townCombobox.ItemsSource = loadOperation.Entities;

townCombobox.DisplayMemberPath = "Name";

townCombobox.SetBinding(ComboBox.SelectedItemProperty, binding);

var item = loadOperation.UserState as DataField;

item.Content = townCombobox;

}

Теперь все почти хорошо, можно проверить, что данные загружаются и связываются как надо. Сейчас есть только проблема с тем, что Combobox всегда является неактивным. Даже во время редактирования. Исправим это следующим кодом:

Добавим имя формы в XAML:

<dataControls:DataForm CurrentItem="{Binding ElementName=customerDataGrid, Path=SelectedItem, Mode=TwoWay}"

AutoGenerateFields="True"

AutoCommit="False"

AutoEdit="False"

Width="400"

EditEnded="DataForm_EditEnded"

AutoGeneratingField="DataForm_AutoGeneratingField"

Name="customerDataForm">

</dataControls:DataForm>

Свяжем свойство IsReadOnlyProperty поля данных с состоянием формы:

private void DataForm_AutoGeneratingField(object sender, DataFormAutoGeneratingFieldEventArgs e)

{

if (e.PropertyName == "Town")

{

var ctx = (customerDomainDataSource.DomainContext as DomainService1);

var townsQ = ctx.Load(ctx.GetTownsQuery(), OnGetTowns, e.Field);

}

}

public void OnGetTowns(LoadOperation loadOperation)

{

Binding binding = new Binding("Town");

binding.Mode = BindingMode.TwoWay;

var townCombobox = new ComboBox();

townCombobox.ItemsSource = loadOperation.Entities;

townCombobox.DisplayMemberPath = "Name";

townCombobox.SetBinding(ComboBox.SelectedItemProperty, binding);

var item = loadOperation.UserState as DataField;

item.Content = townCombobox;

Binding editableBinding = new Binding() { ElementName = "customerDataForm", Path = new PropertyPath("Mode"), Converter = new InverseModeConvertor() };

item.SetBinding(DataField.IsReadOnlyProperty, editableBinding);

}

Добавим преобразователь состояния формы в булевское значение:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows.Data;

using System.Windows.Controls;

namespace HuronLoans.Views

{

class ModeConvertor : IValueConverter

{

#region IValueConverter Members

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

{

var mode = ((DataFormMode)value);

return mode == DataFormMode.Edit || mode == DataFormMode.AddNew;

}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

{

return value;

}

#endregion

}

class InverseModeConvertor : IValueConverter

{

#region IValueConverter Members

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

{

var mode = ((DataFormMode)value);

return !(mode == DataFormMode.Edit || mode == DataFormMode.AddNew);

}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

{

return value;

}

#endregion

}

}

Теперь Combobox должен работать правильно.

clip_image002

Комментариев нет:

Отправить комментарий