I have a simple WPF DataGrid containing numbers that I want to format dynamically based on the state of 2 other WPF controls:
- UserControl with 2 buttons: the precision
- ComboBox: the format specifier
The User Control: Precision Adjuster
XAML:Code behind:< UserControl x :Class ="DataGridFormatting.PrecisionAdjuster"xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns :x ="http://schemas.microsoft.com/winfx/2006/xaml"xmlns :mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns :d ="http://schemas.microsoft.com/expression/blend/2008"mc :Ignorable ="d"d :DesignHeight ="50"d :DesignWidth ="100"> <UserControl.Resources > <Style TargetType ="{x :Type Button }"> <Setter Property ="Width"Value ="30" /> <Setter Property ="Height"Value ="30" /> <Setter Property ="Margin"Value ="3" /> </Style > </UserControl.Resources > <WrapPanel > <Button Command ="{Binding ReducePrecision }"> <Image Source ="Resources/DecimalLess.png" /> </Button > <Button Command ="{Binding IncreasePrecision }"> <Image Source ="Resources/DecimalMore.png" /> </Button > </WrapPanel > </UserControl >
namespace DataGridFormatting {using System.Windows;using System.Windows.Controls;using System.Windows.Data;public partial class PrecisionAdjuster :UserControl {public static readonly DependencyProperty PrecisionProperty =DependencyProperty .Register("Precision" ,typeof (int ),typeof (PrecisionAdjuster ),new PropertyMetadata (default (int )));public int Precision {get {return (int )GetValue(PrecisionProperty); }set { SetValue(PrecisionProperty,value ); } }public PrecisionAdjuster() { InitializeComponent(); DataContext =new PrecisionAdjusterViewModel ();Binding binding =new Binding ("Precision" ) { Mode =BindingMode .TwoWay }; SetBinding(PrecisionProperty, binding); } } }
ViewModel:
namespace DataGridFormatting {using System.ComponentModel;using System.Windows.Input;public class PrecisionAdjusterViewModel :INotifyPropertyChanged {private int precision;public PrecisionAdjusterViewModel() { Precision = 2; ReducePrecision =new RelayCommand (OnReducePrecision); IncreasePrecision =new RelayCommand (OnIncreasePrecision); }public int Precision {get {return precision; }set { precision =value ; OnPropertyChanged("Precision" ); } }public ICommand ReducePrecision {get ;set ; }public ICommand IncreasePrecision {get ;set ; }public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(string propertyName) {PropertyChangedEventHandler handler = PropertyChanged;if (handler !=null ) { handler(this ,new PropertyChangedEventArgs (propertyName)); } }private void OnReducePrecision() {if (Precision > 0) { Precision--; } }private void OnIncreasePrecision() { Precision++; } } }
In the code behind, we bind property Precision of the view-model with DependencyProperty Precision of the UserControl. The DependencyProperty Precision can be set outside of this UserControl, for example as the initial precision value. If this is not set, the default value is 2 as defined in the view-model. Everytime we click the button to increase or reduce precision, the precision is adjusted accordingly within the view-model.
The Main Window
For simplicity, I'm not going to create view-model for the MainWindow. I'll put the test data and the property we're binding to in the code behind:XAML:namespace DataGridFormatting {using System.Collections.ObjectModel;using System.Windows;public partial class MainWindow :Window {public MainWindow() { DataGridSource =new ObservableCollection <ItemRate > {new ItemRate ("AAA" , 3.0123456789),new ItemRate ("BBB" , 1.8478913937),new ItemRate ("CCC" , 2.3891383276),new ItemRate ("DDD" , 1.2334392431) }; InitializeComponent(); }public ObservableCollection <ItemRate > DataGridSource {get ;set ; } } }
(Note: when you copy and paste the below XAML to Visual Studio, it'll not recognize
< Window x :Class ="DataGridFormatting.MainWindow"xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns :x ="http://schemas.microsoft.com/winfx/2006/xaml"xmlns :local ="clr-namespace:DataGridFormatting"Title ="MainWindow"Height ="250"Width ="200"Name ="ThisWindow"> <Window.Resources > <local :NumericFormatConverter x :Key ="MyNumericFormatConverter" /> </Window.Resources > <StackPanel DataContext ="{Binding ElementName =ThisWindow}"> <StackPanel Orientation ="Horizontal"Margin ="10"> <TextBlock Text ="Choose format: "/> <ComboBox x :Name ="FormatSpecifierInput"Width ="40"> <ComboBoxItem Content ="N"IsSelected ="True" /> <ComboBoxItem Content ="P" /> <ComboBoxItem Content ="C" /> </ComboBox > </StackPanel > <local :PrecisionAdjuster x :Name ="MyPrecisionAdjuster"Precision ="4"HorizontalAlignment ="Center" /> <DataGrid ItemsSource ="{Binding DataGridSource }"AutoGenerateColumns ="False"Margin ="10"HorizontalAlignment ="Center"> <DataGrid.Columns > <DataGridTextColumn Header ="Items"Binding ="{BindingItem }" /> <DataGridTemplateColumn Header ="Rates"> <DataGridTemplateColumn.CellTemplate > <DataTemplate > <TextBlock > <TextBlock.Text > <MultiBinding Converter ="{StaticResource MyNumericFormatConverter }"> <Binding Path ="Rate" /> <Binding ElementName ="FormatSpecifierInput"Path ="Text" /> <Binding ElementName ="MyPrecisionAdjuster"Path ="Precision" /> </MultiBinding > </TextBlock.Text > </TextBlock > </DataTemplate > </DataGridTemplateColumn.CellTemplate > </DataGridTemplateColumn > </DataGrid.Columns > </DataGrid > </StackPanel > </Window >
First we create a ComboBox containing the format we want to display our rates in. In this example, the possible values are N (Number), P (Percent) and C (Currency). I'm using .NET standard numeric format strings as described here. Name the ComboBox FormatSpecifierInput, so that we can bind this to the DataGrid later on.
Next we put the UserControl
The DataGrid has 2 columns with
public class ItemRate {public ItemRate(string item,double rate) { Item = item; Rate = rate; }public string Item {get ;set ; }public double Rate {get ;set ; } }
The second column is a
- Property Rate of
ObservableCollection <ItemRate > (bound to property DataGridSource) - Property Text of the ComboBox FormatSpecifierInput
- DependencyProperty Precision of the UserControl MyPrecisionAdjuster
The IMultiValueConverter implementation: NumericFormatConverter
namespace DataGridFormatting {using System;using System.Globalization;using System.Windows.Data;public class NumericFormatConverter :IMultiValueConverter {public object Convert(object [] values,Type targetType,object parameter,CultureInfo culture) {double number = System.Convert .ToDouble(values[0]);string formatSpecifier = System.Convert .ToString(values[1]);int precision = System.Convert .ToInt16(values[2]);string format ="{0:" + formatSpecifier + precision +"}" ;// eg. {0:P4}, {0:N2} return string .Format(format, number); }public object [] ConvertBack(object value,Type [] targetTypes,object parameter,CultureInfo culture) {throw new NotImplementedException(); } } }
Note that the order of
Then we'll need to change< MultiBinding Converter ="{StaticResource MyNumericFormatConverter }"> <Binding ElementName ="FormatSpecifierInput"Path ="Text" /> <Binding Path ="Rate" /> <Binding ElementName ="MyPrecisionAdjuster"Path ="Precision" /> </MultiBinding >
string formatSpecifier = System.Convert .ToString(values[0]);double number = System.Convert .ToDouble(values[1]);int precision = System.Convert .ToInt16(values[2]);
You might want to add some validation in the Convert method to warn your fellow developers to pay attention to the order.
No comments:
Post a Comment