Is there a nullable datepicker that I can bind to? - winforms

I am looking for a datepicker like what microsoft provides, but it doesn't support null values, and since this is tied to a nullable field on a database that isn't acceptable.
I found this one, but according to the comments at the bottom of the page it has issues with binding to a database. I also have one in my project that I inherited, but it has similar issues (sometimes it shows values, sometimes it doesn't). Does anyone know of one that works?

Use a date picker to populate a textbox and if they want the field to be null, just erase the contents of the textbox (and then handle the blank input accordingly).
This also provides the added benefit of allowing the user to type in their date if they so choose.

Smart FieldPackEditor has a datepicker that is nullable. I believe it does everything that you need. I wish this was around when I was dealing with this sort of stuff. I still remember all the workarounds I had to implement with Microsoft's datepicker control. Uggh!
http://www.visualhint.com/index.php/fieldpackeditor/

why not use a client side datepicker to populate a text field. If the textfield is empty, then you have a null date, otherwise convert the value.
jQuery has a nice easy to use datepicker. http://jqueryui.com

This one seems to work, one of my co-workers had it:
using System;
using System.Windows.Forms;
namespace CustomControls
{
public class NullableBindableDateTimePicker : System.Windows.Forms.DateTimePicker
{
private Boolean isNull = false;
private DateTimePickerFormat baseFormat = DateTimePickerFormat.Short;
private Boolean ignoreBindOnFormat = false;
public NullableBindableDateTimePicker()
{
this.Format = baseFormat;
if (baseFormat == DateTimePickerFormat.Custom) this.CustomFormat = " ";
}
public Boolean IsNull
{
get { return isNull; }
set
{
isNull = value;
this.Checked = value;
}
}
//TODO: Add BaseCustomFormat
public DateTimePickerFormat BaseFormat
{
get { return baseFormat; }
set { baseFormat = value; }
}
public object BindMe
{
get
{
if (IsNull) return System.DBNull.Value;
else return base.Value;
}
set
{
//String s = this.Name;
if (ignoreBindOnFormat) return;
if (System.Convert.IsDBNull(value))
{
// for some reason setting base.format in this.format calls set BindMe.
// we need to ignore the following call
ignoreBindOnFormat = true;
this.Format = DateTimePickerFormat.Custom;
ignoreBindOnFormat = false;
this.CustomFormat = " ";
IsNull = true;
}
else
{
ignoreBindOnFormat = true;
this.Format = baseFormat;
ignoreBindOnFormat = false;
if (baseFormat == DateTimePickerFormat.Custom) this.CustomFormat = " ";
IsNull = false;
base.Value = (DateTime)value;
}
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.KeyCode == Keys.Delete)
{
this.BindMe = DBNull.Value;
}
}
protected override void OnCloseUp(EventArgs eventargs)
{
base.OnCloseUp(eventargs);
BindMe = base.Value;
}
}
}

Related

Windows Forms Custom DataGridView

I am not very experienced with Windows Forms and am not pretty sure how I should tackle with this task the best way possible. I have a class which looks like this:
public class VariableMapping
{
private string variableName;
private string variableText;
private string variableSelector;
public VariableMapping(string variableName, string variableText, string variableSelector)
{
this.VariableName = variableName;
this.VariableText = variableText;
this.VariableSelector = variableSelector;
}
public string VariableName
{
get { return this.variableName; }
set { this.variableName = value; }
}
public string VariableText
{
get { return this.variableText; }
set { this.variableText = value; }
}
public string VariableSelector
{
get { return this.variableSelector; }
set { this.variableSelector = value; }
}
}
I want to create a DataGridView which should be bound to a number of elements of type VariableMapping in a list. However, I want only 1 of the properties(VariableText) of every instance to be shown in the DataGridView but I want to be able to address the whole object through the DataGrid when I need to. I also need to add 2 more custom columns: a ComboBox with predefined values and a NumberBox.
It might seem a really simple task but I'm trully unexperienced in WinForms and couldn't find a solution I can use already. Thank you!
Edit: I am trying something like this but it doesn't seem to work properly:
public partial class MappingTable : Form
{
private DataGridView dataGridView1 = new DataGridView();
public MappingTable(List<VariableMapping> variableMappings)
{
InitializeComponent();
var colors = new List<string>() { "#color_k1", "#color_k2", "#color_s1" };
dataGridView1.AutoGenerateColumns = false;
dataGridView1.AutoSize = true;
dataGridView1.DataSource = variableMappings;
DataGridViewColumn titleColumn = new DataGridViewColumn();
titleColumn.DataPropertyName = "VariableText";
titleColumn.HeaderText = "Variable";
titleColumn.Name = "Variable*";
dataGridView1.Columns.Add(titleColumn);
DataGridViewComboBoxColumn colorsColumn = new DataGridViewComboBoxColumn();
colorsColumn.DataSource = colors;
colorsColumn.HeaderText = "Color";
dataGridView1.Columns.Add(colorsColumn);
DataGridViewTextBoxColumn opacityColumn = new DataGridViewTextBoxColumn();
opacityColumn.HeaderText = "Opacity";
dataGridView1.Columns.Add(opacityColumn);
this.Controls.Add(dataGridView1);
this.AutoSize = true;
}
}

Can you replace DatePickerTextBox with a different TextBox in DatePicker and still get the validation functionality?

I replaced the DatePickerTextBox in the control template of DatePicker, and it no longer ensures a valid date value for the text entered. I tested different strings with a plain DatePickerTextBox, too, and it didn't validate them, so it must be some kind of interaction between DatePickerTextBox and DatePicker. Is there a way to get that interaction with a different TextBox control in the control template that isn't DatePickerTextBox, or will I have to recreate that validation in my own custom control?
Short explanation with answer:
In the OnApplyTemplate method of a custom TextBox control, I added:
public override void OnApplyTemplate()
{
LostFocus += OnLostFocus;
base.OnApplyTemplate();
}
And then the method:
private void OnLostFocus(object sender, RoutedEventArgs e)
{
if (!Text.Equals(oldText))
{
DateTime d;
if (!DateTime.TryParse(Text, out d))
{
Text = oldText;
}
}
oldText = Text;
}
Long explanation of why it doesn't work in the control template if you replace DatePickerTextBox:
I poked around in the reference source and discovered the following:
In the OnApplyTemplate method for DatePicker, some handlers are added, specifically for a DatePickerTextBox, which it will not apply if it doesn't find one (which explains why DatePickerTextBox doesn't validate on its own, either - DatePicker adds the handlers, not DatePickerTextBox):
_textBox = GetTemplateChild(ElementTextBox) as DatePickerTextBox;
if (this.SelectedDate == null)
{
SetWaterMarkText();
}
if (_textBox != null)
{
_textBox.AddHandler(TextBox.KeyDownEvent, new KeyEventHandler(TextBox_KeyDown), true);
_textBox.AddHandler(TextBox.TextChangedEvent, new TextChangedEventHandler(TextBox_TextChanged), true);
_textBox.AddHandler(TextBox.LostFocusEvent, new RoutedEventHandler(TextBox_LostFocus), true);
if (this.SelectedDate == null)
{
if (!string.IsNullOrEmpty(this._defaultText))
{
_textBox.Text = this._defaultText;
SetSelectedDate();
}
}
else
{
_textBox.Text = this.DateTimeToString((DateTime)this.SelectedDate);
}
}
So, tracking it down(here comes the fun part)...
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
SetSelectedDate();
}
Then:
private void SetSelectedDate()
{
if (this._textBox != null)
{
if (!string.IsNullOrEmpty(this._textBox.Text))
{
string s = this._textBox.Text;
if (this.SelectedDate != null)
{
// If the string value of the SelectedDate and the TextBox string value are equal,
// we do not parse the string again
// if we do an extra parse, we lose data in M/d/yy format
// ex: SelectedDate = DateTime(1008,12,19) but when "12/19/08" is parsed it is interpreted as DateTime(2008,12,19)
string selectedDate = DateTimeToString(this.SelectedDate.Value);
if (string.Compare(selectedDate, s, StringComparison.Ordinal) == 0)
{
return;
}
}
DateTime? d = SetTextBoxValue(s);
if (!this.SelectedDate.Equals(d))
{
this.SetCurrentValueInternal(SelectedDateProperty, d);
this.SetCurrentValueInternal(DisplayDateProperty, d);
}
}
else
{
if (this.SelectedDate.HasValue)
{
this.SetCurrentValueInternal(SelectedDateProperty, (DateTime?)null);
}
}
}
else
{
DateTime? d = SetTextBoxValue(_defaultText);
if (!this.SelectedDate.Equals(d))
{
this.SetCurrentValueInternal(SelectedDateProperty, d);
}
}
}
The important thing there is the call to SetTextBoxValue:
private DateTime? SetTextBoxValue(string s)
{
if (string.IsNullOrEmpty(s))
{
SafeSetText(s);
return this.SelectedDate;
}
else
{
DateTime? d = ParseText(s);
if (d != null)
{
SafeSetText(this.DateTimeToString((DateTime)d));
return d;
}
else
{
// If parse error:
// TextBox should have the latest valid selecteddate value:
if (this.SelectedDate != null)
{
string newtext = this.DateTimeToString((DateTime)this.SelectedDate);
SafeSetText(newtext);
return this.SelectedDate;
}
else
{
SetWaterMarkText();
return null;
}
}
}
}
And the important thing there is the call to ParseText:
private DateTime? ParseText(string text)
{
DateTime newSelectedDate;
// TryParse is not used in order to be able to pass the exception to the TextParseError event
try
{
newSelectedDate = DateTime.Parse(text, DateTimeHelper.GetDateFormat(DateTimeHelper.GetCulture(this)));
if (Calendar.IsValidDateSelection(this._calendar, newSelectedDate))
{
return newSelectedDate;
}
else
{
DatePickerDateValidationErrorEventArgs dateValidationError = new DatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException("text", SR.Get(SRID.Calendar_OnSelectedDateChanged_InvalidValue)), text);
OnDateValidationError(dateValidationError);
if (dateValidationError.ThrowException)
{
throw dateValidationError.Exception;
}
}
}
catch (FormatException ex)
{
DatePickerDateValidationErrorEventArgs textParseError = new DatePickerDateValidationErrorEventArgs(ex, text);
OnDateValidationError(textParseError);
if (textParseError.ThrowException && textParseError.Exception != null)
{
throw textParseError.Exception;
}
}
return null;
}
The last two methods have the validation I was looking for. If ParseText returns null, then SetTextBoxValue reverts the value in the text box back to its previous value. Because DatePicker is the control that adds the handler, you won't get the validation outside of DatePicker. Since DatePicker looks specifically for a DatePickerTextBox named "PART_TextBox" (that is what the ElementTextBox string is), a DatePickerTextBox must be used, or the handler will not be applied (since _textbox will be null).

How can I validate multiple properties when any of them changes?

I have two date fields: StartDate and EndDate. StartDate must be earlier than EndDate.
If the user changes the StartDate to something greater than the EndDate, a red border appears around that DatePicker, and vise versa. If the user changes the 2nd box so that the date range is now correct, the 1st box still has the Validation Error.
How can I validate both date fields when either one of them changes?
I'm using IDataErrorInfo
public string GetValidationError(string propertyName)
{
switch (propertyName)
{
case "StartDate":
if (StartDate > EndDate)
s = "Start Date cannot be later than End Date";
break;
case "EndDate":
if (StartDate > EndDate)
s = "End Date cannot be earlier than Start Date";
break;
}
return s;
}
I cannot simply raise a PropertyChange event because I need to validate both fields when either of them changes, so having both of them raise a PropertyChange event for the other will get stuck in an infinite loop.
I also do not like the idea of clearing the Date field if the other date returns a validation error.
The simplest way is to raise a PropertyChanged notification for in the setter for both properties that need to be validated like bathineni suggests
private DateTime StartDate
{
get { return _startDate; }
set
{
if (_startDate != value)
{
_startDate = value;
RaisePropertyChanged("StartDate");
RaisePropertyChanged("EndDate");
}
}
}
private DateTime EndDate
{
get { return _endDate; }
set
{
if (_endDate!= value)
{
_endDate= value;
RaisePropertyChanged("StartDate");
RaisePropertyChanged("EndDate");
}
}
}
However if that doesn't work for you, I figured out one way to validate a group of properties together, although your classes have to implement INotifyPropertyChanging in addition to INotifyPropertyChanged (I'm using EntityFramework and by default their classes implement both interfaces)
Extension Method
public static class ValidationGroup
{
public delegate string ValidationDelegate(string propertyName);
public delegate void PropertyChangedDelegate(string propertyName);
public static void AddValidationGroup<T>(this T obj,
List<string> validationGroup, bool validationFlag,
ValidationDelegate validationDelegate,
PropertyChangedDelegate propertyChangedDelegate)
where T : INotifyPropertyChanged, INotifyPropertyChanging
{
// This delegate runs before a PropertyChanged event. If the property
// being changed exists within the Validation Group, check for validation
// errors on the other fields in the group. If there is an error with one
// of them, set a flag to true.
obj.PropertyChanging += delegate(object sender, PropertyChangingEventArgs e)
{
if (validationGroup.Contains(e.PropertyName))
{
foreach(var property in validationGroup)
{
if (validationDelegate(property) != null)
{
validationFlag = true;
break;
}
}
}
};
// After the Property gets changed, if another field in this group was
// invalid prior to the change, then raise the PropertyChanged event for
// all other fields in the Validation Group to update them.
// Also turn flag off so it doesn't get stuck in an infinite loop
obj.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
{
if (validationGroup.Contains(e.PropertyName))
{
if (validationFlag && validationDelegate(e.PropertyName) == null)
{
validationFlag = false;
foreach(var property in validationGroup)
{
propertyChangedDelegate(property);
}
}
}
};
}
}
To use it, add the following call to the constructor of any class that should validate a group of properties together.
this.AddValidationGroup(
new List<string> { "StartDate", "EndDate" },
GetValidationError, OnPropertyChanged);
I've tested this with up to 3 properties in a Validation Group and it seems to work OK.
Use this trick, it prevents they call OnPropertyChanged each other :
private bool RPCfromStartDate = false;
private bool RPCfromEndDate = false;
public string this[string columnName]
{
get
{
string result = null;
switch (columnName)
{
case "StartDate":
if (StartDate.Date >= EndDate.Date)
{
result = "Start Date cannot be later than End Date";
}
if (!RPCfromEndDate)
{
RPCfromStartDate = true;
OnPropertyChanged("EndDate");
RPCfromStartDate = false;
}
case "EndDate":
if (StartDate.Date >= EndDate.Date)
{
result = "End Date cannot be earlier than Start Date";
}
if (!RPCfromStartDate)
{
RPCfromEndDate = true;
OnPropertyChanged("StartDate");
RPCfromEndDate = false;
}
break;
}
...
I generally add all of my validation errors to a dictionary, and have the validation template subscribe to that via the property name. In each property changed event handler, I can check any number of properties and add or remove their validation status as necessary.
Check this answer for how my implementation looks. Sorry it is in VB.NET, but should be fairly straightforward.
You can also subscribe to the SelectedDateChanged event handler and update needed binding.
BindingExpression expression = datePickerStartDate.GetBindingExpression(DatePicker.SelectedDateProperty);
if (expression != null)
{
expression.UpdateSource();
}

WPF-Multiple Views on ObservableCollection

I have a viewmodel containing two CollectionViews defined.
One I am using for navigation and data entry/edit.
Another I want to use for filtering purpose and show the filteration in some Listview on the form.
I don't want the main view(used for DataEntry purpose) to get affected while I applying filteration on observablecollection.
Thanks in Advance!
As long as you're using separate collection views, changing one won't affect the other. That is the point of collection views - they're independent views on the same collection.
ok, Got it! and went ahead with the same idea. But when I did so, I get Error = "The calling thread cannot access this object because a different thread owns it.". Hence my filteration doesn't work.. Following is the code-
public ICollectionView Clients { get; set; } //Used for Data-navigation/modification
public ListCollectionView CodeView { get; set; } // to be used for filteration purpose on form.
string searchText = String.Empty;
public string CompanyCodeSearch
{
get { return searchText; }
set
{
try
{
searchText = value;
OnPropertyChanged("CompanyCodeSearch");
CodeView.Filter = new Predicate<object>(cmFilterData);
}
catch (Exception ex)
{
}
}
}
private bool cmFilterData(object item)
{
bool _filteredData = false;
try
{
var value = (item as cntClient);
if (value == null || value.CompanyCode == null)
return false;
_filteredData = value.CompanyCode.StartsWith(this.CompanyCodeSearch);
return _filteredData;
}
catch (Exception ex)
{
return false;
}
}

Silverlight MVVM Validation in a DataForm

I am using generic data classes, so I can't use ria services attributes to control my validation - so I am looking for a way to manualy set up validation to work in a DataForm.
public partial class DataValue
{
private Dictionary<string, string> _errors = new Dictionary<string, string>();
public Dictionary<string, string> Errors
{
get { return _errors; }
}
public Object Value
{
get
{
object result = new object();
switch ((DataType)this.ModelEntity.ModelItem.DataType)
{
case DataType.Money:
return result = this.ValueText.ParseNullableFloat();
case DataType.Integer:
return result = this.ValueText.ParseNullableInt();
case DataType.Date:
case DataType.Time:
return result = this.ValueText.ParseNullableDateTime();
case DataType.CheckBox:
return result = this.ValueText;
default:
return result = this.ValueText;
}
}
set
{
if (!String.IsNullOrEmpty(value.ToString()))
{
bool invalid = false;
switch ((DataType)this.ModelEntity.ModelItem.DataType)
{
case DataType.Money:
float val;
if (!float.TryParse(value.ToString(), out val)) invalid = true;
break;
case DataType.Integer:
int val2;
if (!Int32.TryParse(value.ToString(), out val2)) invalid = true;
break;
case DataType.Date:
case DataType.Time:
DateTime val3;
if (!DateTime.TryParse(value.ToString(), out val3)) invalid = true;
break;
}
if (invalid == false)
ValueText = value.ToString();
else
{
ValueText = "";
_errors.Add(this.ModelEntity.LocalName, "error writing " + value.ToString() + " to " + this.ModelEntity.ModelItem.Label);
}
}
else
ValueText = "";
}
}
public partial class ModelValidater : INotifyPropertyChanging, INotifyPropertyChanged
{
private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
private int _ModelValidatorId;
private int _ModelEntityId;
private int _ValidatorType;
private string _ValidatorParameters;
So in ASP MVC, I simply manually checked against these rules when the form was submitted... which I guess is pretty much what I want to do in MVVM (I am just not sure the best way to go about this).
ASP Code
protected bool ModelErrors(RecordDictionary record)
{
bool result = false;
foreach (var field in record)
{
foreach (var error in field.Value.Errors)
{
result = true;
ModelState.AddModelError(error.Key + "Validation", error.Value.ToString());
}
}
return result;
}
Silverlight 3 built-in validation is based on exceptions.
Just throw a meaningful exception in your generic Setter and you should be fine.
Remember to set ValidatesOnException=True and NotifyOnValidationError=True on your {Binding}.
Jesse has a good sample of validation with exceptions on his blog.
You can attach the validation attributes using the MetadataTypeAttribute attribute.
RIA Services will automatically generate these validation on the client for you once they're exposed in the DomainService.
Example:
[MetadataType(typeof(ContactMd))]
public partial class Contact
{
internal class ContactMd
{
[MyCustomValidation]
public string Name { get; set; }
}
}
(MyCustomValidation refers to anything that inherits from ValidationAttribute).

Resources