With the following code, although Text property is bound to a DateTime source property, I noticed WPF seems to automatically convert the text to a DateTime, without me needing to write a ValueConverter. Can someone please shed some light on how this is done
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
>
<StackPanel>
<DatePicker Height="25" Name="datePicker1" Width="213" Text="{Binding Path=DueDate,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Window>
public class P
{
private DateTime? dueDate = DateTime.Now;
public DateTime? DueDate
{
get { return dueDate; }
set
{
dueDate = value;
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
P p = new P();
this.DataContext = p;
}
}
It is using the DateTimeTypeConverter from the Base Class Library (EDIT: Well, it could have used a TypeConverter however it appears that from #DeviantSeev's answer that they did not).
There 'default' converters you are talking about are actually TypeConverters (MSDN) and they have been a part of the .NET Framework since v2.0 and they are used through-out the Base Class Libraries. Another example of TypeConverters in WPF is the ThicknessTypeConverter for Padding, Margin, and BorderThickness properties. It converts a comma-delimited string to a Thickness object.
There are plenty of articles available if you want to understand them further.
There are two parts to using a TypeConverter - implementation of the class and then marking up your properties/types with TypeConverterAttribute.
For example, I recently had a custom control that required a char[] that I wanted to set from Xaml like so:
<AutoCompleteTextBox MultiInputDelimiters=",;. " />
Usage
[TypeConverter(typeof(CharArrayTypeConverter))]
public char[] MultiInputDelimiters
{
get { return (char[])GetValue(MultiInputDelimitersProperty); }
set { SetValue(MultiInputDelimitersProperty, value); }
}
Implementation
public class CharArrayTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (Type.GetTypeCode(sourceType) == TypeCode.String);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
return ((string)value).ToCharArray();
return value;
}
}
When to use a TypeConverter?
You can only use TypeDescriptors if you are writing a custom control as you need to be able to mark-up the property with the TypeDescriptorAttribute. Also I would only use TypeConverter if the conversion is rather a straight-forward - as in the example above where I have a string and want a char[] - or if there are multiple possible formats that I want to convert from.
You write IValueConverter when you want more flexibility on how the value to converted by driving it by data or a passing a parameter. For example, a very common action in WPF is converting a bool to Visibility; there are three possible outputs from such a conversion (Visible, Hidden, Collapsed) and with only two inputs (true, false) it difficult to decide this in a TypeConverter.
In my applications, to achieve this two inputs to three output problem I have written a single BoolToVisibilityConverter with a TrueValue and FalseValue properties and then I instance it three times in my global ResourceDictionary. I'll post the code sample tomorrow morning, I don't it in front of me right now..
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BooleanToVisibilityConverter : IValueConverter
{
public Visibility FalseCondition { get; set; }
public Visibility TrueCondition { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value) ? TrueCondition : FalseCondition;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
return TrueCondition;
return FalseCondition;
}
}
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" FalseCondition="Collapsed" TrueCondition="Visible"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityCollapsedConverter" FalseCondition="Visible" TrueCondition="Collapsed"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHiddenConverter" FalseCondition="Visible" TrueCondition="Hidden"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHiddenWhenFalseConverter" FalseCondition="Hidden" TrueCondition="Visible"/>
The DatePicker is a custom control that was initially part of the WPF Toolkit before being added as a standard control in .NET 4.
I just went to the source code repository for the control to find you the exact source code which is responsible for the conversion of the text to date:
#region Text
/// <summary>
/// Gets or sets the text that is displayed by the DatePicker.
/// </summary>
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <summary>
/// Identifies the Text dependency property.
/// </summary>
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(DatePicker),
new FrameworkPropertyMetadata(string.Empty, OnTextChanged, OnCoerceText));
/// <summary>
/// TextProperty property changed handler.
/// </summary>
/// <param name="d">DatePicker that changed its Text.</param>
/// <param name="e">DependencyPropertyChangedEventArgs.</param>
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DatePicker dp = d as DatePicker;
Debug.Assert(dp != null);
if (!dp.IsHandlerSuspended(DatePicker.TextProperty))
{
string newValue = e.NewValue as string;
if (newValue != null)
{
if (dp._textBox != null)
{
dp._textBox.Text = newValue;
}
else
{
dp._defaultText = newValue;
}
dp.SetSelectedDate();
}
else
{
dp.SetValueNoCallback(DatePicker.SelectedDateProperty, null);
}
}
}
private static object OnCoerceText(DependencyObject dObject, object baseValue)
{
DatePicker dp = (DatePicker)dObject;
if (dp._shouldCoerceText)
{
dp._shouldCoerceText = false;
return dp._coercedTextValue;
}
return baseValue;
}
/// <summary>
/// Sets the local Text property without breaking bindings
/// </summary>
/// <param name="value"></param>
private void SetTextInternal(string value)
{
if (BindingOperations.GetBindingExpressionBase(this, DatePicker.TextProperty) != null)
{
Text = value;
}
else
{
_shouldCoerceText = true;
_coercedTextValue = value;
CoerceValue(TextProperty);
}
}
#endregion Text
In most cases I believe WPF is calling ToString() for you however if you look at the code for the date picker the important line is
(string)GetValue(TextProperty)
notice it casts the value you assigned to the "Text" property to a string? The whole point is there is no default converter in the more traditional sense of BooleanToVisibilityConverter or something like that.
Related
Boolean is for storing economically in SQL Database. But the datagridview when using datasource function in C# just show true or false by the checkbox each row.
I want to display the string value in the datagridview, not Boolean using checkbox .
True = "Spin On"
False = "Element"
How can I change the checkbox to a string value?
You have at least two options to obtain this:
Binding
First, you could change the binding during on RowDataBound of GridView. Give a look at this example, where you have the following class:
public class Student
{
public int Roll { get; set; }
public string Name { get; set; }
public bool Status { get; set; }
}
and you can change the default behavior of GridView as follows:
/// <summary>
/// Handles the RowDataBound event of the GridView2 control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Web.UI.WebControls.GridViewRowEventArgs"/> instance containing the event data.</param>
protected void GridView2_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
Student s = (Student)e.Row.DataItem;
if (s.Status == true)
{
e.Row.Cells[2].Text = "1";
}
else
{
e.Row.Cells[2].Text = "0";
}
}
}
Formatting
Another way to address this problem consists in using custom formatting, handling the CellFormatting event according to your needs:
void gridView2_CellFormatting(object s, DataGridViewCellFormattingEventArgs evt)
{
if (evt.ColumnIndex == yourcolumnIndex){
if (evt.Value is bool){
evt.Value = ((bool)evt.Value) ? "Yes" : "No";
evt.FormattingApplied = true;
}
}
}
Using a Converter will allow you to do it easily
using System;
using System.Windows.Data;
namespace WpfApplication2
{
[ValueConversion(typeof(Boolean), typeof(String))]
internal class BooleanToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Boolean state = (Boolean)value;
return state ? "Spin On" : "Element";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
then, in your XAML, bind your String (a label in my example) to the Boolean ('state' variable in the example) using the Converter :
<Label Content="{Binding State, Converter={StaticResource BooleanToStringConverter }, Mode=OneWay}" />
I am trying to bind a dependency property to an INotifyPropertyChanged-enabled property with a multi-level property path.
When the owner object of the property is not null, the binding works, but if the owner object is null, the binding does not do anything (the converter is not called and TargetNullValue is not used).
Here is some minimal sample code to reproduce the problem:
Window1.xaml.cs:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;
namespace NPCPropertyPath
{
public abstract class VMBase : INotifyPropertyChanged
{
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (e == null) {
throw new ArgumentNullException("e");
}
if (PropertyChanged != null) {
PropertyChanged(this, e);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class Window1 : Window
{
private class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) {
return null;
} else if (value is int) {
return (((int)value) + 15).ToString();
} else {
return "no int";
}
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
private class InnerVM : VMBase
{
private int myValue;
public int MyValue {
get {
return myValue;
}
set {
if (myValue != value) {
myValue = value;
OnPropertyChanged("MyValue");
}
}
}
}
private class OuterVM : VMBase
{
private InnerVM thing;
public InnerVM Thing {
get {
return thing;
}
set {
if (thing != value) {
thing = value;
OnPropertyChanged("Thing");
}
}
}
}
private readonly OuterVM vm = new OuterVM();
public Window1()
{
InitializeComponent();
var txt = new TextBlock();
txt.SetBinding(TextBlock.TextProperty,
new Binding("Thing.MyValue") {
Source = vm,
Mode = BindingMode.OneWay,
Converter = new MyConverter(),
TargetNullValue = "(error)"
});
container.Content = txt;
var txt2 = new TextBlock();
txt2.SetBinding(TextBlock.TextProperty,
new Binding("Thing") {
Source = vm,
Mode = BindingMode.OneWay,
Converter = new MyConverter(),
TargetNullValue = "(error)"
});
container2.Content = txt2;
}
void Button_Click(object sender, RoutedEventArgs e)
{
vm.Thing = new InnerVM();
vm.Thing.MyValue += 10;
}
}
}
Window1.xaml:
<Window x:Class="NPCPropertyPath.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NPCPropertyPath" Height="300" Width="300">
<StackPanel>
<Button Content="Change value" Click="Button_Click"/>
<ContentControl Name="container"/>
<ContentControl Name="container2"/>
</StackPanel>
</Window>
Of course, this is a significantly simplified form of my real application, where there is quite a bit more going on and the involved classes are not crammed together in two files (or even in the same assembly) where they can all see each other.
This sample displays a window with a button and two content controls. Each of the content controls contains a TextBlock whose Text property is bound to a property from a view-model. The view-model instance (of type OuterVM) is assigned to the Source property of each binding.
OuterVM implements INotifyPropertyChanged; it has a property Thing of type InnerVM (which also implements INotifyPropertyChanged), which in turn has a property MyValue.
The first text block is bound to Thing.MyValue, the second one just to Thing. Both of the bindings have a converter set, as well as a value for the TargetNullValue property that should be displayed on the text block if the target property is null.
The Thing property of the OuterVM instance is initially null. When clicking the button, something is assigned to that property.
The problem: Only the second text block displays anything initially. The one that is bound to Thing.MyValue neither invokes the converter (as evidenced by setting breakpoints), nor does it use the value of the TargetNullValue property.
Why? And how can I have the first text block display a default value instead of Thing.MyValue while Thing is not assigned?
For this purpose you should not use TargetNullValue, you should use FallbackValue property of the Binding.
I'm using a XamDataTree and I need the node icons to have different images for every type of node. I tried asking this on Infragistics, but we don't understand each other :(
Also the problem really has nothing to do with their control anymore, it's about how to get an image stored in the model to show. Yes this is against the principles of MVVM, but this is the only workable way of showing the images.
Note: I have a workaround, use a Label to specify the node contents and put a horizontal stackpanel in it that has the image and node text in it. But this is crappy, I want the XamDataGrid object to show the image.
There are 28 different images to show here, some of which are created dynamically. So I don't want to make a ton of different templates that load the images by file path and then use different templates.
The XamDataTree, I also tried specifying the binding with Element name or !.
I'm trying to load the image directly from the model or through a DataTemplate:
<ig:XamDataTree
ScrollViewer.CanContentScroll="True"
ItemsSource="{Binding Model}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
NodeLineVisibility="Visible"
>
<ig:XamDataTree.Resources>
<DataTemplate x:Key="nodeImage" >
<Image Source="{Binding Path=NodeImage}" />
</DataTemplate>
</ig:XamDataTree.Resources>
<ig:XamDataTree.GlobalNodeLayouts>
<ig:NodeLayout
ExpandedIconTemplate="{StaticResource nodeImage}"
CollapsedIconTemplate="{Binding NodeImage}"
Key="Children"
DisplayMemberPath="Name"
TargetTypeName="Model"
>
</ig:NodeLayout>
</ig:XamDataTree.GlobalNodeLayouts>
</ig:XamDataTree>
The model that I use, the image source value is set elsewhere:
public class Model
{
/// <summary>
/// Image representing the type of node this is.
/// </summary>
public ImageSource NodeImage
{
get
{
return this.nodeImage;
}
set
{
this.nodeImage = value;
this.OnPropertyChanged("NodeImage");
}
}
/// <summary>
/// Controls will bind their attributes to this event handler.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Fire an event alerting listners a property changed.
/// </summary>
/// <param name="name">Name of the property that changed.</param>
public void OnPropertyChanged(string name)
{
// Check whether a control is listening.
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Edit:
I tried loading the images directly from files into a BitmapImage, then storing them in the model, din't work. When using higher debugging level messaging it posts messages in the output that the databinding cannot find the corrosponding property. So the problem is most likely in my data binding.
Another solution would be to use a converter and the type Stream for your property NodeImage.
Converter:
public class StreamToImageConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
var imageStream = value as System.IO.Stream;
if (imageStream != null)) {
System.Windows.Media.Imaging.BitmapImage image = new System.Windows.Media.Imaging.BitmapImage();
image.SetSource(imageStream );
return image;
}
else {
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
Property:
private Stream _imageStream;
public Stream NodeImage
{
get
{
return _imageStream;
}
set
{
_imageStream= value;
this.OnPropertyChanged("NodeImage");
}
}
XAML:
<Grid>
<Grid.Resources>
<local:StreamToImageConverter x:Name="imageConverter"/>
</Grid.Resources>
<Image Source="{Binding Path=NodeImage, Converter={StaticResource imageConverter}" />
</Grid>
Change your property NodeImage to type string and return the path of the Image. The rest is done by the Image and the converter classes that convert your path to a ImageSource.
private string _imagePath;
public string NodeImage
{
get
{
return _imagePath;
}
set
{
_imagePath = value;
this.OnPropertyChanged("NodeImage");
}
}
My models use enums for fixed multiple selections. I'm using Dean Chalk's EnumBinder, found at http://deanchalk.me.uk/post/Enumeration-Binding-In-Silverlight.aspx , to bind to a combo box. Everything seems to work great except the default value isn't shown. The selected index is -1, and it doesn't matter if I bind to SelectedItem or SelectedValue. The combobox works fine otherwise. I have no problems with default values with any other bound comboboxes.
enumbindingsupport.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
/* All of this was once just part of the RichClient sub-namespace
* but it has its uses elsewhere, so it has been moved.
*/
/// <summary>
/// Container for enumeration values.
/// </summary>
/// <remarks>
/// http://deanchalk.me.uk/post/Enumeration-Binding-In-Silverlight.aspx
/// </remarks>
public sealed class EnumContainer
{
public int EnumValue { get; set; }
public string EnumDescription { get; set; }
public object EnumOriginalValue { get; set; }
public override string ToString() {
return EnumDescription;
}
public override bool Equals(object obj) {
if (obj == null)
return false;
if (obj is EnumContainer)
return EnumValue.Equals((int)((EnumContainer)obj).EnumValue);
return EnumValue.Equals((int)obj);
}
public override int GetHashCode() {
return EnumValue.GetHashCode();
}
}
/// <summary>
/// A collection to store a list of EnumContainers that hold enum values.
/// </summary>
/// <remarks>
/// http://deanchalk.me.uk/post/Enumeration-Binding-In-Silverlight.aspx
/// </remarks>
/// <typeparam name="T"></typeparam>
public class EnumCollection<T> : List<EnumContainer> where T : struct
{
public EnumCollection() {
var type = typeof(T);
if (!type.IsEnum)
throw new ArgumentException("This class only supports Enum types");
var fields = typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public);
foreach (var field in fields) {
var container = new EnumContainer();
container.EnumOriginalValue = field.GetValue(null);
container.EnumValue = (int)field.GetValue(null);
container.EnumDescription = field.Name;
var atts = field.GetCustomAttributes(false);
foreach (var att in atts)
if (att is DescriptionAttribute) {
container.EnumDescription = ((DescriptionAttribute)att).Description;
break;
}
Add(container);
}
}
}
enumvalueconverter.cs
using System;
using System.Globalization;
using System.Windows.Data;
/// <summary>
/// Supports two-way binding of enumerations.
/// </summary>
public class EnumValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture) {
return (int)value;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture) {
if (value == null)
return null;
if (value.GetType() == targetType)
return value;
return ((EnumContainer)value).EnumOriginalValue;
}
}
enum used:
/// <summary>
/// Describes the available sorting options.
/// </summary>
public enum PeopleSortOptionsEnum
{
[Display(Order = 10)]
[Description("First Name, Last Name")]
FirstNameThenLastName,
[Display(Order = 20)]
[Description("Last Name, First Name")]
LastNameThenFirstName,
[Display(Order = 30)]
Grade,
[Display(Order = 40)]
Gender,
[Display(Order = 50)]
Age
}
property on my model:
/// <summary>
/// This is the list for the enumeration to bind to.
/// </summary>
public EnumCollection<PeopleSortOptionsEnum> AvailableSortOptions
{
get { return new EnumCollection<PeopleSortOptionsEnum>(); }
}
XAML snippet:
<ComboBox ItemsSource="{Binding Path=AvailableSortOptions, Mode=OneWay}" SelectedValue="{Binding Path=Preferences.SortOrder, Mode=TwoWay, Converter={StaticResource EnumConverter}}" Grid.Column="1" Grid.Row="2" Height="32" Margin="48,31,0,0" x:Name="cboSort" VerticalAlignment="Top" />
Where Preferences.SortOrder is of type PeopleSortOptionsEnum, the converter is in my app.xaml as a converter for all enum types.
Anyone have any idea why it won't set the index to the currently selected value? I'm about to just throw some code in the codebehind to set the selectedindex to the currently selected value on load, but I feel so dirty just thinking about it.
Besides this issue, it works wonderfully, so thanks Dean!
Edit Adding the Preferences.SortOrder code:
public PeopleSortOptionsEnum SortOrder
{
get { return sortOrder; }
set
{
if (sortOrder != value)
{
sortOrder = value;
PropertyHasChanged("SortOrder");
}
}
}
The issue is the Convert method on your enum converter class:
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture) {
return (int)value;
}
When the combobox SelectedItem is gets the value from SortOrder it is return in Enum value which this converter is converting to an int. However, the combobox items is a collection of EnumContainer objects, not ints. So it fails to set the selected item.
To resolve the issue you have to do two things. First change your combobox bindings and set the SelectedValuePath:
<ComboBox ItemsSource="{Binding Path=AvailableSortOptions}"
SelectedValue="{Binding Path=SortOrder, Mode=TwoWay, Converter={StaticResource EnumConverter}}"
SelectedValuePath="EnumOriginalValue"/>
Second you have to slightly modify the Convert method on your converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
Once I made those two changes it started working as expected.
I'm trying to make ValidationRule which depends on some property from (for example) data model.
I have TextBox with validator which have to know about the model object "Scheme". I've tryed to add Scheme into Resources but this didn't work. And after I've found some solution relying on dependency properties.
According to this http://dedjo.blogspot.com/2007/05/fully-binded-validation-by-using.html I've made:
/// <summary>
/// Check text value for emptiness and uniqueness
/// </summary>
public class EmptyAndUnique : ValidationRule
{
public UniqueChecker UniqueChecker { get; set; }
public string ErrorMessage { get; set; }
public override ValidationResult Validate(object value,
CultureInfo cultureInfo)
{
string inputString = (value).ToString();
var result = new ValidationResult(true, null);
// Check uniqueness
if(this.UniqueChecker.Scheme.Factors.Any(f => f.Uid == inputString))
{
result = new ValidationResult(false, this.ErrorMessage);
return result;
}
// Check emptiness
if (inputString.Trim().Equals(string.Empty))
{
result = new ValidationResult(false, this.ErrorMessage);
return result;
}
return result;
}
}
/// <summary>
/// Wrapper for DependencyProperty. (trick)
/// </summary>
public class UniqueChecker : DependencyObject
{
public static readonly DependencyProperty SchemeProperty =
DependencyProperty.Register("Scheme", typeof(Scheme),
typeof(UniqueChecker), new FrameworkPropertyMetadata(null));
public Scheme Scheme
{
get { return (Scheme)GetValue(SchemeProperty); }
set { SetValue(SchemeProperty, value); }
}
}
This does not work either.
1) According to article:
Scheme="{Binding ElementName=expressionFactorEditorWindow, Path=Scheme}"
this does not work because:
cos our dependency object is not part
of logical tree, so you can not use
ElementName or DataContext as source
for internal data binding.
But why it's not part of logical tree?
2) How can I bind properties of my ValidationRule to some dynamic resources
UPDATE
While watching for better solution I made this:
Add Event which checks uniqueness into ValidationRule
Add Handler into Window class
Raise event from ValidationRule and check result from EventArgs
What are you binding to that you are trying to add Validation for. How I usually handle this by having the object I am binding to implement IDataErrorInfo. Then I can put my error handling in that object and have access to anything I need. This would be possible if you are in control of the object you are Binding to.