понедельник, 30 августа 2010 г.

Формат данных в DataForm в Silverlight 4

Теперь рассмотрим задачу измения формата полей данных в DataGrid. Как легко заметить, элемент DataForm является крайне полезным, но в то же время его очень сложно кастомизировать.

Предположим снова, что у нас имеется некоторая таблица с большим количеством полей, то есть мы не можем просто описать каждое поле отдельно и форме с данными. То есть нам нужно каким-то образом задавать формат данных через файл с метаданными.

Пусть мы хотим задать формат валюты для некоторых полей. Для этого существует специальный аттрибут DisplayFormat, в котором как раз можно задать формат денежных единиц следующим образом:

[DisplayFormat(DataFormatString = "{0:C}")]

Но, как можно уже было догадаться, в Silverlight этот аттрибут просто не работает… По крайней мере в DataForm, то есть как раз там, где он больше всего нужен.

Но мы не будем печалиться и напишем свой код, который будет решать эту проблему (по крайней мере – частично).

Для начала нам нужно будет повесить какой-нибудь аттрибут к денежным полям сущности (можно повесить, например, DisplayFormat, хотя это абсолютно неважно).

Файл с метаданными может выглядеть таким образом:

[MetadataTypeAttribute(typeof(ChargeOff.ChargeOffMetadata))]

public partial class ChargeOff

{

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

// of the ChargeOff 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 ChargeOffMetadata

{

// Metadata classes are not meant to be instantiated.

private ChargeOffMetadata()

{

}

[Display(Name = "Shared-Loss Month", Order = 0)]

public Nullable<DateTime> Shared_Loss_Month { get; set; }

[Display(Name = "Interest Paid-to date", Order = 1)]

public Nullable<DateTime> Interest_Paid_to_date { get; set; }

[Display(Name = "Charge-off date", Order = 2)]

public Nullable<DateTime> Charge_off_date { get; set; }

[Display(Name = "Valuation Date", Order = 3)]

public Nullable<DateTime> Valuation_Date { get; set; }

[Display(Name = "Valuation Amount", Order = 4)]

[DisplayFormat(DataFormatString = "{0:C}")]

public Nullable<double> Valuation_Amount { get; set; }

[Display(Name = "Valuation Type", Order = 5)]

[DisplayFormat(DataFormatString = "{0:C}")]

public Nullable<double> Valuation_Type { get; set; }

[Display(Name = "Balance of superior liens", Order = 6)]

[DisplayFormat(DataFormatString = "{0:C}")]

public Nullable<double> Balance_of_superior_liens { get; set; }

[Display(Name = "Loan Principal Balance", Order = 7)]

[DisplayFormat(DataFormatString = "{0:C}")]

public Nullable<double> Loan_Principal_Balance { get; set; }

[Display(Name = "Charge-off Amount", Order = 8)]

[DisplayFormat(DataFormatString = "{0:C}")]

public Nullable<double> Charge_off_Amount { get; set; }

[Display(Name = "Accrued interest", Order = 9)]

[DisplayFormat(DataFormatString = "{0:C}")]

public Nullable<double> Accrued_interest { get; set; }

[Display(Name = "FDIC Adjustment for Accrued Interest", Order = 10)]

[DisplayFormat(DataFormatString = "{0:C}")]

public Nullable<double> FDIC_Adjustment_for_Accrued_Interest { get; set; }

[Display(Name = "FDIC Adjustment", Order = 11)]

[DisplayFormat(DataFormatString = "{0:C}")]

public Nullable<double> FDIC_Adjustment { get; set; }

[Display(Name = "Note", Order = 12)]

public string Note { get; set; }

[Display(AutoGenerateField = false)]

public int ID { get; set; }

[Display(AutoGenerateField = false)]

public Loan Loan { get; set; }

[Display(AutoGenerateField = false)]

public int LoanID { get; set; }

[Display(AutoGenerateField = false)]

public bool IsManual { get; set; }

}

}

Этот аттрибут мы будем самостоятельно обрабатывать в коде, так что добавим обработчик события автоматической генерацтии полей с нашей форме данных.

<fw:FloatableWindow x:Class="MyApplication.Views.LoanView"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"

xmlns:fw="clr-namespace:System.Windows.Controls;assembly=FloatableWindow"

xmlns:df="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"

Width="650" Height="600"

Title="Loan Edit" xmlns:my="clr-namespace:MyApplication.Controls" Loaded="ChildWindow_Loaded" mc:Ignorable="d"

xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices"

xmlns:my1="clr-namespace:MyApplication.Web.Services"

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

xmlns:my2="clr-namespace:MyApplication.BLL"

xmlns:converter="clr-namespace:MyApplication"

xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"

HorizontalAlignment="Left" VerticalAlignment="Top">

<Grid x:Name="LayoutRoot">

<ScrollViewer>

<StackPanel>

<my:BusyIndicator IsBusy="{Binding ElementName=chargeOffDomainDataSource, Path=DomainContext.IsLoading}" BusyContent="Loading..." >

<StackPanel>

<sdk:DataGrid AutoGenerateColumns="False" Name="chargeOffDataGrid"

RowDetailsVisibilityMode="VisibleWhenSelected" ColumnWidth="Auto"

IsReadOnly="True" SelectionMode="Single"

Margin="0,0,0,10"

ItemsSource="{Binding Path=Data, ElementName=chargeOffDomainDataSource}"

>

<sdk:DataGrid.Columns>

<sdk:DataGridTextColumn x:Name="chargeOffDateColumn" Binding="{Binding Path=Charge_off_date}" Header="Date" />

<sdk:DataGridTextColumn x:Name="chargeOffAmountColumn" Binding="{Binding Path=Charge_off_Amount}" Header="Amount" />

</sdk:DataGrid.Columns>

</sdk:DataGrid>

<riaControls:DomainDataSource AutoLoad="True" d:DesignData="{d:DesignInstance my2:Loan, CreateList=true}" Height="0" LoadedData="chargeOffDomainDataSource_LoadedData" Name="chargeOffDomainDataSource" QueryName="GetChargeOffsByLoanID" Width="0">

<riaControls:DomainDataSource.DomainContext>

<my1:MyApplicationDomainContext />

</riaControls:DomainDataSource.DomainContext>

<riaControls:DomainDataSource.QueryParameters>

<riaControls:Parameter ParameterName="loanID" />

</riaControls:DomainDataSource.QueryParameters>

</riaControls:DomainDataSource>

</StackPanel>

</my:BusyIndicator>

<my:CustomDataForm HorizontalAlignment="Center" x:Name="chargeOffDataForm" VerticalAlignment="Top" Width="450" AutoEdit="False" AutoCommit="True" AutoGenerateFields="True"

CommandButtonsVisibility="Add, Delete, Edit, Commit, Cancel" ItemsSource="{Binding Path=Data, ElementName=chargeOffDomainDataSource}" CurrentItem="{Binding Path=SelectedItem, ElementName=chargeOffDataGrid, Mode=TwoWay}"

EditEnded="chargeOffDataForm_EditEnded" ContentLoaded="chargeOffDataForm_ContentLoaded"

AutoGeneratingField="chargeOffDataForm_AutoGeneratingField"

DeletingItem="chargeOffDataForm_DeletingItem" Style="{StaticResource AddRemoveDataFromStyle}">

</my:CustomDataForm>

</StackPanel>

</ScrollViewer>

</Grid>

</fw:FloatableWindow>

Теперь нам нужно будем заново связать поля с данными (точнее настроить конвертер данных). Создадим следующий конвертер, который просто форматирует данные при помощи указанной строки формата.

using System;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Ink;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

using System.Windows.Data;

namespace MyApplication

{

public class DataFormatConverter : IValueConverter

{

private string _format;

public DataFormatConverter(string format)

{

_format = format;

}

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

{

return string.Format(_format, value);

}

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

{

if (value == null)

return null;

return string.IsNullOrEmpty(value.ToString()) ? null : value.ToString();

}

}

}

Можно использовать и более простой конвертер, он будет поддерживать только денежный формат данных. Код такого конвертера я приведу, на всякий случай, но использовать его мы не будем.

using System;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Ink;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

using System.Windows.Data;

namespace MyApplication

{

public class CurrencyConverter : IValueConverter

{

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

{

return string.Format("{0:C}", value);

}

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

{

if (value == null)

return null;

return string.IsNullOrEmpty(value.ToString()) ? null : value.ToString();

}

}

}

Теперь напишем в обработчике событие следующий код:

private void chargeOffDataForm_AutoGeneratingField(object sender, DataFormAutoGeneratingFieldEventArgs e)

{

if (e.Field.Content.GetType() == typeof(TextBox))

{

if (e.PropertyType == typeof(Nullable<double>))

{

// should be currency

var format = ((sender as DataForm).CurrentItem).GetType().GetProperty(e.PropertyName).GetCustomAttributes(typeof(DisplayFormatAttribute), true).Cast<DisplayFormatAttribute>().FirstOrDefault();

if (format != null)

{

Binding binding = e.Field.Content.GetBindingExpression(TextBox.TextProperty).ParentBinding.CreateCopy();

binding.Converter = new DataFormatConverter(format.DataFormatString);

e.Field.Content.SetBinding(TextBox.TextProperty, binding);

}

}

}

}

В этом коде мы получаем тип поля данных через рефлекшн, проверяем, указан ли для данного поля аттрибут DisplayFormat. Если это так, то воспользуемся нашим новым конвертером для этого поля данных.

Также можно сделать так, чтобы денежные поля были выровнены по правому краю. Можно было бы создать еще один аттрибут и поступить по аналогии с прошлым примером, но сейчас я ограничусь простой проверкой строки формата. Если это строка “0:С” (то есть денежный формат), то можно выравнивать данные по правому краю.

private void chargeOffDataForm_AutoGeneratingField(object sender, DataFormAutoGeneratingFieldEventArgs e)

{

if (e.Field.Content.GetType() == typeof(TextBox))

{

if (e.PropertyType == typeof(Nullable<double>))

{

// should be currency

var format = ((sender as DataForm).CurrentItem).GetType().GetProperty(e.PropertyName).GetCustomAttributes(typeof(DisplayFormatAttribute), true).Cast<DisplayFormatAttribute>().FirstOrDefault();

if (format != null)

{

if (format.DataFormatString == "{0:C}")

{

(e.Field.Content as TextBox).TextAlignment = TextAlignment.Right;

}

Binding binding = e.Field.Content.GetBindingExpression(TextBox.TextProperty).ParentBinding.CreateCopy();

binding.Converter = new DataFormatConverter(format.DataFormatString);

e.Field.Content.SetBinding(TextBox.TextProperty, binding);

}

}

}

}

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

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