Keep WPF Calendar open after date selection from DatePicker - wpf

I'm attempting to customise the WPF DatePicker and it's calendar popup component to keep the popup open after the user has selected a date. This is due to the extension to the style I have made that allows the user to select a time.
The calendar should only be closed once the user has clicked a 'finished' button in the style.
I've made multiple attempts at doing this, each with varying degrees of success but I have to admit, a fully working solution has eluded me.
Example solution using an override on IsDropDownOpen;
IsDropDownOpenProperty.OverrideMetadata(typeof(CustomDatePicker), new
FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnIsDropDownOpenChanged, OnCoerceIsDropDownOpen));
private static object OnCoerceIsDropDownOpen(DependencyObject d, object baseValue)
{
var instance = d as CustomDatePicker;
if (instance != null)
{
if (instance.CalendarPopup.IsOpen == false)
{
return true;
}
var coalesed = (bool) baseValue;
if (coalesed == false)
{
return instance.PopupStaysOpenAfterSelection;
}
}
return baseValue;
}
'PopupStaysOpenAfterSelection' is a dependency property I have created so that this behaviour is customizable.
Is an example of the popup staying open and closing when the user clicks 'done' however the popup will no longer reopen after interacting with the datepicker button.
Am I missing something obvious with this approach / is there a working solution?
EDIT:
ROOT CONTROL
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using Calendar = System.Windows.Controls.Calendar;
namespace CommonControls.Calendar
{
[TemplatePart(Name = NavigationPreviousButtonPartName, Type = typeof(Button))]
[TemplatePart(Name = NavigationNextButtonPartName, Type = typeof(Button))]
[TemplatePart(Name = NavigationTodayButtonPartName, Type = typeof(Button))]
[TemplatePart(Name = NavigationDoneButtonPartName, Type = typeof(Button))]
[TemplatePart(Name = DatePickerButtonPartName, Type = typeof(Button))]
[TemplatePart(Name = DatePickerTextBoxPartName, Type = typeof(DatePickerTextBox))]
[TemplatePart(Name = CalendarPopupPartName, Type = typeof(Popup))]
[TemplatePart(Name = DatePickerDisplayTextBoxPartName, Type = typeof(TextBlock))]
public class ExtendedDatePicker : DatePicker
{
public const string DatePickerDisplayTextBoxPartName = "PART_DisplayDateTextBox";
public const string DatePickerButtonPartName = "PART_Button";
public const string DatePickerTextBoxPartName = "PART_TextBox";
public const string CalendarPopupPartName = "PART_Popup";
public const string NavigationPreviousButtonPartName = "PART_NavPreviousButton";
public const string NavigationNextButtonPartName = "PART_NavNextButton";
public const string NavigationTodayButtonPartName = "PART_TodayButton";
public const string NavigationDoneButtonPartName = "PART_DoneButton";
public static readonly DependencyProperty BlackoutDatesInPastProperty =
DependencyProperty.Register("BlackoutDatesInPast", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(true, BlackoutDatesInPastPropertyChanged));
public static readonly DependencyProperty ThrowExceptionOnBlackoutDateSelectionProperty =
DependencyProperty.Register("ThrowExceptionOnBlackoutDateSelection", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(false));
public static readonly DependencyProperty DatePickerButtonTemplateProperty =
DependencyProperty.Register("DatePickerButtonTemplate", typeof(ControlTemplate), typeof(ExtendedDatePicker), new PropertyMetadata(null));
public static readonly DependencyProperty CustomDateFormatProperty =
DependencyProperty.Register("CustomDateFormat", typeof(string), typeof(ExtendedDatePicker), new PropertyMetadata("d", CustomDateFormatPropertyChanged));
public static readonly DependencyProperty UseCustomDateFormatProperty =
DependencyProperty.Register("UseCustomDateFormat", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(false, UseCustomDateFormatPropertyChanged));
public static readonly DependencyProperty ShowTodayButtonProperty =
DependencyProperty.Register("ShowTodayButton", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(false, ShowTodayButtonPropertyChanged));
public static readonly DependencyProperty DaysToJumpProperty =
DependencyProperty.Register("DaysToJump", typeof(int), typeof(ExtendedDatePicker), new PropertyMetadata(1));
public static readonly DependencyProperty MonthsToJumpProperty =
DependencyProperty.Register("MonthsToJump", typeof(int), typeof(ExtendedDatePicker), new PropertyMetadata(1));
public static readonly DependencyProperty PopupStaysOpenAfterSelectionProperty =
DependencyProperty.Register("PopupStaysOpenAfterSelection", typeof(bool),
typeof(ExtendedDatePicker), new PropertyMetadata(null));
protected DatePickerTextBox DatePickerTextBox;
protected Calendar InternalCalendar;
protected Popup CalendarPopup;
protected bool PendingClose = false;
private Button _navigationPreviousButton;
private Button _navigationNextButton;
private Button _navigationTodayButton;
private Button _navigationDoneButton;
private Button _datePickerButton;
private TextBlock _displayDateTextBlock;
static ExtendedDatePicker()
{
IsDropDownOpenProperty.OverrideMetadata(typeof(ExtendedDatePicker), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ExtendedOnIsDropDownOpenChanged, OnCoerceIsDropDownOpen));
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExtendedDatePicker), new FrameworkPropertyMetadata(typeof(ExtendedDatePicker)));
}
private static object OnCoerceIsDropDownOpen(DependencyObject d, object basevalue)
{
var instance = d as ExtendedDatePicker;
if (!instance.IsEnabled)
{
return false;
}
if (!instance.PopupStaysOpenAfterSelection)
{
return basevalue;
}
if (instance.PendingClose)
{
instance.PendingClose = false;
return false;
}
return true;
}
/**
* OLD
private static object OnCoerceIsDropDownOpen(DependencyObject d, object baseValue)
{
var instance = d as ExtendedDatePicker;
if (instance != null)
{
if (instance.IsDropDownOpen == false)
{
return true;
}
var coalesed = (bool) baseValue;
if (coalesed == false)
{
return instance.PopupStaysOpenAfterSelection;
}
}
return baseValue;
}
*/
private static void ExtendedOnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = (ExtendedDatePicker) d;
instance.CoerceValue(IsDropDownOpenProperty);
}
public bool BlackoutDatesInPast
{
get { return (bool)GetValue(BlackoutDatesInPastProperty); }
set { SetValue(BlackoutDatesInPastProperty, value); }
}
public bool ThrowExceptionOnBlackoutDateSelection
{
get { return (bool)GetValue(ThrowExceptionOnBlackoutDateSelectionProperty); }
set { SetValue(ThrowExceptionOnBlackoutDateSelectionProperty, value); }
}
public ControlTemplate DatePickerButtonTemplate
{
get { return (ControlTemplate)GetValue(DatePickerButtonTemplateProperty); }
set { SetValue(DatePickerButtonTemplateProperty, value); }
}
public string CustomDateFormat
{
get { return (string)GetValue(CustomDateFormatProperty); }
set { SetValue(CustomDateFormatProperty, value); }
}
public bool UseCustomDateFormat
{
get { return (bool)GetValue(UseCustomDateFormatProperty); }
set { SetValue(UseCustomDateFormatProperty, value); }
}
public bool PopupStaysOpenAfterSelection
{
get { return (bool) GetValue(PopupStaysOpenAfterSelectionProperty); }
set { SetValue(PopupStaysOpenAfterSelectionProperty, value);}
}
public bool ShowTodayButton
{
get { return (bool)GetValue(ShowTodayButtonProperty); }
set { SetValue(ShowTodayButtonProperty, value); }
}
public int DaysToJump
{
get { return (int)GetValue(DaysToJumpProperty); }
set { SetValue(DaysToJumpProperty, value); }
}
public int MonthsToJump
{
get { return (int)GetValue(MonthsToJumpProperty); }
set { SetValue(MonthsToJumpProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_datePickerButton = GetTemplateChild(DatePickerButtonPartName) as Button;
_datePickerButton.CreateBinding(TemplateProperty, this, nameof(DatePickerButtonTemplate));
DatePickerTextBox = GetTemplateChild(DatePickerTextBoxPartName) as DatePickerTextBox;
_displayDateTextBlock = GetTemplateChild(DatePickerDisplayTextBoxPartName) as TextBlock;
CalendarPopup = GetTemplateChild(CalendarPopupPartName) as Popup;
InternalCalendar = CalendarPopup.Child as Calendar;
InternalCalendar.ApplyTemplate();
CalendarPopup.Loaded += CalendarPopup_Loaded;
_navigationPreviousButton = GetTemplateChild(NavigationPreviousButtonPartName) as Button;
_navigationNextButton = GetTemplateChild(NavigationNextButtonPartName) as Button;
_navigationTodayButton = InternalCalendar.Template.FindName(NavigationTodayButtonPartName, InternalCalendar) as Button;
_navigationDoneButton = InternalCalendar.Template.FindName(NavigationDoneButtonPartName, InternalCalendar) as Button;
if (_navigationTodayButton != null)
{
_navigationTodayButton.Click += TodayButton_Click;
}
if (_navigationPreviousButton != null)
{
_navigationPreviousButton.Click += NavPreviousButton_Click;
}
if (_navigationNextButton != null)
{
_navigationNextButton.Click += NavNextButton_Click;
}
if (_navigationDoneButton != null)
{
_navigationDoneButton.Click += NavDoneButton_Click;
}
RefreshBlackOutDates();
RefreshDisplayDate();
RefreshTextControlsVisibility();
RefreshTodayButtonVisibility();
}
private void CalendarPopup_Loaded(object sender, RoutedEventArgs e)
{
}
protected override void OnSelectedDateChanged(SelectionChangedEventArgs e)
{
base.OnSelectedDateChanged(e);
RefreshDisplayDate();
}
private static void BlackoutDatesInPastPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var source = (ExtendedDatePicker)d;
source.RefreshBlackOutDates();
}
private static void CustomDateFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var source = (ExtendedDatePicker)d;
source.RefreshDisplayDate();
}
private static void UseCustomDateFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var source = (ExtendedDatePicker)d;
source.RefreshTextControlsVisibility();
source.RefreshDisplayDate();
}
private static void ShowTodayButtonPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var source = (ExtendedDatePicker)d;
source.RefreshTodayButtonVisibility();
}
private void RefreshTextControlsVisibility()
{
if (DatePickerTextBox != null
&& _displayDateTextBlock != null)
{
DatePickerTextBox.Visibility = UseCustomDateFormat ? Visibility.Collapsed : Visibility.Visible;
_displayDateTextBlock.Visibility = !UseCustomDateFormat ? Visibility.Collapsed : Visibility.Visible;
}
}
private void RefreshTodayButtonVisibility()
{
if (_navigationTodayButton != null)
{
_navigationTodayButton.Visibility = ShowTodayButton ? Visibility.Visible : Visibility.Collapsed;
}
}
private void RefreshDisplayDate()
{
if (_displayDateTextBlock != null)
{
if (UseCustomDateFormat
&& !string.IsNullOrEmpty(CustomDateFormat))
{
if (SelectedDate.HasValue)
{
var d = SelectedDate.Value;
_displayDateTextBlock.Text = string.Format(CultureInfo.CurrentCulture, d.ToString(CustomDateFormat));
}
else
{
_displayDateTextBlock.Text = "Select a date";
}
}
}
}
private void RefreshBlackOutDates()
{
if (BlackoutDatesInPast)
{
BlackoutDates.AddDatesInPast();
}
}
private void GoToToday()
{
var newDate = DateTime.Today;
GoToDate(newDate);
}
private void GoToNextDate()
{
var selectedDate = SelectedDate;
if (selectedDate.HasValue)
{
var newDate = selectedDate.Value;
if (DaysToJump > 0)
{
newDate = newDate.AddDays(DaysToJump);
}
if (MonthsToJump > 0)
{
newDate = newDate.AddMonths(MonthsToJump);
}
GoToDate(newDate);
}
}
private void GoToPreviousDate()
{
var selectedDate = SelectedDate;
if (selectedDate.HasValue)
{
var newDate = selectedDate.Value;
if (DaysToJump > 0)
{
newDate = newDate.AddDays(DaysToJump * -1);
}
if (MonthsToJump > 0)
{
newDate = newDate.AddMonths(MonthsToJump * -1);
}
GoToDate(newDate);
}
}
private void GoToDate(DateTime date)
{
if (!BlackoutDates.Contains(date))
{
SelectedDate = date;
}
}
private void NavNextButton_Click(object sender, RoutedEventArgs e)
{
GoToNextDate();
}
private void NavPreviousButton_Click(object sender, RoutedEventArgs e)
{
GoToPreviousDate();
}
private void TodayButton_Click(object sender, RoutedEventArgs e)
{
GoToToday();
}
private void NavDoneButton_Click(object sender, RoutedEventArgs e)
{
IsDropDownOpen = false;
}
}
}
WITH TIME
public class CustomDatePickerWithTime : ExtendedDatePicker
{
public static readonly DependencyProperty MinuteIntervalProperty = DependencyProperty.Register(
"MinuteInterval", typeof(int), typeof(ExtendedDatePickerWithTime), new FrameworkPropertyMetadata(30));
public static readonly DependencyPropertyKey AvailableTimesPropertyKey = DependencyProperty.RegisterReadOnly(
"AvailableTimes", typeof(List<TimeSpan>), typeof(ExtendedDatePickerWithTime),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));
public static readonly DependencyProperty AvailableTimesProperty = AvailableTimesPropertyKey.DependencyProperty;
public static readonly DependencyProperty SelectedTimeProperty = DependencyProperty.Register(
"SelectedTime", typeof(TimeSpan), typeof(ExtendedDatePickerWithTime), new PropertyMetadata(default(TimeSpan), OnSelectedTimePropertyChanged));
static ExtendedDatePickerWithTime()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExtendedDatePickerWithTime),
new FrameworkPropertyMetadata(typeof(ExtendedDatePickerWithTime)));
}
public int MinuteInterval
{
get { return (int) GetValue(MinuteIntervalProperty); }
set { SetValue(MinuteIntervalProperty, value);}
}
public List<TimeSpan> AvailableTimes
{
get { return (List<TimeSpan>) GetValue(AvailableTimesProperty); }
protected set { SetValue(AvailableTimesPropertyKey, value);}
}
public TimeSpan SelectedTime
{
get { return (TimeSpan) GetValue(SelectedTimeProperty); }
set { SetValue(SelectedTimeProperty, value);}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CheckIntervalConstraintsAndConstruct();
OverrideTextBoxEntryStyle();
}
private void OverrideTextBoxEntryStyle()
{
}
private static void OnSelectedTimePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = (ExtendedDatePickerWithTime) d;
instance.SelectedDate = instance.SelectedDate?.Date.Add(instance.SelectedTime) ?? DateTime.Today.Add(instance.SelectedTime);
}
private void CheckIntervalConstraintsAndConstruct()
{
if (60 % MinuteInterval != 0)
{
throw new ArgumentOutOfRangeException(nameof(MinuteInterval), #"The supplied interval in minutes must be divisible by 60");
}
var times = new List<TimeSpan>();
var starting = DateTime.Today;
var ending = DateTime.Today.AddDays(1);
for (var ts = starting; ts <= ending.AddMinutes(MinuteInterval *-1); ts = ts.AddMinutes(MinuteInterval))
{
times.Add(ts.TimeOfDay);
}
AvailableTimes = times;
}
}
XAML: (length exceeds allowed body)
https://pastebin.com/W9KrtM8E

Related

WPF - Access DependencyObject inside a CollectionChanged event

I have an user control that combines the values of 2 DepedenciyProperties:
int numberPeople and ingredients Lists<>
I want when either of these values updates to redo the combination.
My current implementation uses a static variable to keep track of the object instance (objectInstance). I am wondering if there is a cleaner way to do this.
private static DependencyObject objectInstance;
public int numberPeople
{
get { return (int)GetValue(numberPeopleProperty); }
set { SetValue(numberPeopleProperty, value); }
}
public static readonly DependencyProperty numberPeopleProperty =
DependencyProperty.Register("numberPeople", typeof(int), typeof(ListDisplayer), new PropertyMetadata(0, Combine));
public ObservableCollection<ListModel> ingredients
{
get { return (ObservableCollection<ListModel>)GetValue(ingredientsProperty); }
set { SetValue(ingredientsProperty, value); }
}
public static readonly DependencyProperty ingredientsProperty =
DependencyProperty.Register("ingredients", typeof(ObservableCollection<ListModel>), typeof(ListDisplayer), new PropertyMetadata(null, AssignCollectionChangedToList));
private static void AssignCollectionChangedToList(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as ListDisplayer;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
coll.CollectionChanged -= ItemsSource_CollectionChanged;
}
if (e.NewValue != null)
{
instance.ItemsSource = (ObservableCollection<ListModel>)e.NewValue;
objectInstance = instance;
instance.ItemsSource.CollectionChanged += ItemsSource_CollectionChanged;
}
}
private static void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// MY PROBLEM: when a new item is added in this list trigger again Combine(), is there
// another way to trigger the Combine so that it will process the IngredientList and numberPeople attached to the object ?
Combine(objectInstance, new DependencyPropertyChangedEventArgs());
}
private static void Combine(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// process numberPeople and ingredientList
}
Edit: Remove the static keyword from the definition of the ItemsSource_CollectionChanged event handler and hook it up using the "instance" reference:
private static void AssignCollectionChangedToList(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as ListDisplayer;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
coll.CollectionChanged -= instance.ItemsSource_CollectionChanged;
}
if (e.NewValue != null)
{
instance.ingredients = (ObservableCollection<ListModel>)e.NewValue;
objectInstance = instance;
instance.ingredients.CollectionChanged += instance.ItemsSource_CollectionChanged;
}
}
private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Combine(this, new DependencyPropertyChangedEventArgs());
}
Also the CLR wrappers of your dependency properties are not correctly implemented. You should pass the dependency property to the GetValue and SetValue methods:
public int numberPeople
{
get { return (int)GetValue(numberPeopleProperty); }
set { SetValue(numberPeopleProperty, value); }
}
public static readonly DependencyProperty numberPeopleProperty =
DependencyProperty.Register("numberPeople", typeof(int), typeof(ListDisplayer), new PropertyMetadata(0, Combine));
public ObservableCollection<ListModel> ingredients
{
get { return (ObservableCollection<ListModel>)GetValue(ingredientsProperty); }
set { SetValue(ingredientsProperty, value); }
}
public static readonly DependencyProperty ingredientsProperty =
DependencyProperty.Register("ingredients", typeof(ObservableCollection<ListModel>), typeof(ListDisplayer), new PropertyMetadata(null, AssignCollectionChangedToList));

Creating a functional Double Control

I have a custom control called DoubleNumericBox that validates and accepts user input like 23,00, 0,9, 23.900,01, 34... etc.
The problem starts when I try to binding something to it. The binding is not reliable enough, some times the control won't display the new value, but if I set the DataContext one more time it will set the value, etc.
So, I must be doing something very wrong with my custom properties and events.
Custom Properties/Events
Value : Double
MinValue : Double
MaxValue : Double
ValueChanged : Event
Expected Behaviour
Validate typed keys: Numbers, Commas and Points (Decimal separator and digit grouping glyph). My culture uses Comma as decimal separator.
Validate the whole text if (return to the latest Value if number not valid):
Text pasted.
Lost Focus.
Validate Min/Max limit.
Accept binding from Text or Value, and validate the binding value.
Code
public class DoubleNumericBox : TextBox
{
Variables:
public readonly static DependencyProperty MinValueProperty;
public readonly static DependencyProperty ValueProperty;
public readonly static DependencyProperty MaxValueProperty;
Properties:
public double MinValue
{
get { return (double)GetValue(MinValueProperty); }
set { SetCurrentValue(MinValueProperty, value); }
}
public double Value
{
get { return (double)GetValue(ValueProperty); }
set
{
SetCurrentValue(ValueProperty, value);
RaiseValueChangedEvent();
}
}
public double MaxValue
{
get { return (double)GetValue(MaxValueProperty); }
set { SetCurrentValue(MaxValueProperty, value); }
}
Event:
public static readonly RoutedEvent ValueChangedEvent;
public event RoutedEventHandler ValueChanged
{
//Provide CLR accessors for the event
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
public void RaiseValueChangedEvent()
{
var newEventArgs = new RoutedEventArgs(ValueChangedEvent);
RaiseEvent(newEventArgs);
}
Constructor/Override:
static DoubleNumericBox()
{
MinValueProperty = DependencyProperty.Register("MinValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D));
ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D, ValueCallback));
MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(Double.MaxValue));
ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DoubleNumericBox));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
PreviewTextInput += DoubleNumericBox_PreviewTextInput;
ValueChanged += DoubleNumericBox_ValueChanged;
TextChanged += DoubleNumericBox_TextChanged;
LostFocus += DoubleNumericBox_LostFocus;
AddHandler(DataObject.PastingEvent, new DataObjectPastingEventHandler(PastingEvent));
}
Events:
private static void ValueCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = d as DoubleNumericBox;
if (textBox == null) return;
//textBox.Text = String.Format("{0:###,###,##0.0###}", textBox.Value);
textBox.RaiseValueChangedEvent();
}
private void DoubleNumericBox_ValueChanged(object sender, RoutedEventArgs e)
{
var textBox = sender as DoubleNumericBox;
if (textBox == null) return;
ValueChanged -= DoubleNumericBox_ValueChanged;
TextChanged -= DoubleNumericBox_TextChanged;
if (Value > MaxValue)
Value = MaxValue;
else if (Value < MinValue)
Value = MinValue;
textBox.Text = Text = String.Format("{0:###,###,##0.0###}", Value);
ValueChanged += DoubleNumericBox_ValueChanged;
TextChanged += DoubleNumericBox_TextChanged;
}
private void DoubleNumericBox_TextChanged(object sender, TextChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
if (String.IsNullOrEmpty(textBox.Text)) return;
if (IsTextDisallowed(textBox.Text)) return;
ValueChanged -= DoubleNumericBox_ValueChanged;
var newValue = Convert.ToDouble(textBox.Text);
if (newValue > MaxValue)
Value = MaxValue;
else if (newValue < MinValue)
Value = MinValue;
else
{
Value = newValue;
}
ValueChanged += DoubleNumericBox_ValueChanged;
}
private void DoubleNumericBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (String.IsNullOrEmpty(e.Text))
{
e.Handled = true;
return;
}
//Only Numbers, comma and points.
if (IsEntryDisallowed(sender, e.Text))
{
e.Handled = true;
}
}
private void PastingEvent(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(String)))
{
var text = (String)e.DataObject.GetData(typeof(String));
if (IsTextDisallowed(text))
{
e.CancelCommand();
}
}
else
{
e.CancelCommand();
}
}
private void DoubleNumericBox_LostFocus(object sender, RoutedEventArgs e)
{
TextChanged -= DoubleNumericBox_TextChanged;
Text = String.Format("{0:###,###,##0.0###}", Value);
TextChanged += DoubleNumericBox_TextChanged;
}
Methods:
private bool IsEntryDisallowed(object sender, string text)
{
var regex = new Regex(#"^[0-9]|\.|\,$");
if (regex.IsMatch(text))
{
return !CheckPontuation(sender, text);
}
//Not a number or a Comma/Point.
return true;
}
private bool IsTextDisallowed(string text)
{
var regex = new Regex(#"^((\d+)|(\d{1,3}(\.\d{3})+)|(\d{1,3}(\.\d{3})(\,\d{3})+))((\,\d{4})|(\,\d{3})|(\,\d{2})|(\,\d{1})|(\,))?$");
return !regex.IsMatch(text); //\d+(?:,\d{1,2})?
}
private bool CheckPontuation(object sender, string next)
{
var textBox = sender as TextBox;
if (textBox == null) return true;
if (Char.IsNumber(next.ToCharArray()[0]))
return true;
if (next.Equals("."))
{
var textAux = textBox.Text;
if (!String.IsNullOrEmpty(textBox.SelectedText))
textAux = textAux.Replace(textBox.SelectedText, "");
//Check if the user can add a point mark here.
var before = textAux.Substring(0, textBox.SelectionStart);
var after = textAux.Substring(textBox.SelectionStart);
//If no text, return true.
if (String.IsNullOrEmpty(before) && String.IsNullOrEmpty(after)) return true;
if (!String.IsNullOrEmpty(before))
{
if (before.Contains(',')) return false;
if (after.Contains("."))
{
var split = before.Split('.');
if (split.Last().Length != 3) return false;
}
}
if (!String.IsNullOrEmpty(after))
{
var split = after.Split('.', ',');
if (split.First().Length != 3) return false;
}
return true;
}
//Only one comma.
if (next.Equals(","))
{
return !textBox.Text.Any(x => x.Equals(','));
}
return true;
}
}
Can you guys help me out to make this custom control work better?
So a couple of gotchas I see in your code:
Do not use += / -= to hook up events in WPF controls, it can and will break routed events, use Addhandler / RemoveHandler instead.
I removed the unhooking and rehooking of events and used a member level flag instead for change loop issues. Here is the code I came up with, seem to bind fine to Value field.
A side note, you failed to account for multiple "." entry in your textbox so a user could type 345.34.434.23 which would not be prevented. I know to check this because I wrote a WPF FilterTextBox years ago and this came up in my testing.
public class DoubleNumericBox : TextBox
{
public readonly static DependencyProperty MinValueProperty;
public readonly static DependencyProperty ValueProperty;
public readonly static DependencyProperty MaxValueProperty;
public bool _bIgnoreChange = false;
public double MinValue
{
get { return (double)GetValue(MinValueProperty); }
set { SetCurrentValue(MinValueProperty, value); }
}
public double Value
{
get { return (double)GetValue(ValueProperty); }
set
{
SetCurrentValue(ValueProperty, value);
RaiseValueChangedEvent();
}
}
public double MaxValue
{
get { return (double)GetValue(MaxValueProperty); }
set { SetCurrentValue(MaxValueProperty, value); }
}
public static readonly RoutedEvent ValueChangedEvent;
public event RoutedEventHandler ValueChanged
{
//Provide CLR accessors for the event
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
public void RaiseValueChangedEvent()
{
var newEventArgs = new RoutedEventArgs(ValueChangedEvent);
RaiseEvent(newEventArgs);
}
static DoubleNumericBox()
{
MinValueProperty = DependencyProperty.Register("MinValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D));
ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D, ValueCallback));
MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(Double.MaxValue));
ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DoubleNumericBox));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
AddHandler(TextBox.PreviewTextInputEvent, new TextCompositionEventHandler(DoubleNumericBox_PreviewTextInput));
AddHandler(TextBox.TextChangedEvent, new TextChangedEventHandler(DoubleNumericBox_TextChanged));
AddHandler(TextBox.LostFocusEvent, new RoutedEventHandler(DoubleNumericBox_LostFocus));
AddHandler(DataObject.PastingEvent, new DataObjectPastingEventHandler(PastingEvent));
}
private static void ValueCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = d as DoubleNumericBox;
if (textBox == null) return;
//textBox.Text = String.Format("{0:###,###,##0.0###}", textBox.Value);
textBox.DoubleNumericBox_ValueChanged();
}
private void DoubleNumericBox_ValueChanged()
{
if (Value > MaxValue)
Value = MaxValue;
else if (Value < MinValue)
Value = MinValue;
if (!_bIgnoreChange)
this.Text = Text = String.Format("{0:###,###,##0.0###}", Value);
}
private void DoubleNumericBox_TextChanged(object sender, TextChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
if (String.IsNullOrEmpty(textBox.Text)) return;
if (IsTextDisallowed(textBox.Text)) return;
//ValueChanged -= DoubleNumericBox_ValueChanged;
_bIgnoreChange = true;
Value = Convert.ToDouble(textBox.Text);
//if (newValue > MaxValue)
// Value = MaxValue;
//else if (newValue < MinValue)
// Value = MinValue;
//else
//{
// Value = newValue;
//}
_bIgnoreChange = false;
//ValueChanged += DoubleNumericBox_ValueChanged;
}
private void DoubleNumericBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (String.IsNullOrEmpty(e.Text))
{
e.Handled = true;
return;
}
//Only Numbers, comma and points.
if (IsEntryDisallowed(sender, e.Text))
{
e.Handled = true;
}
}
private void PastingEvent(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(String)))
{
var text = (String)e.DataObject.GetData(typeof(String));
if (IsTextDisallowed(text))
{
e.CancelCommand();
}
}
else
{
e.CancelCommand();
}
}
private void DoubleNumericBox_LostFocus(object sender, RoutedEventArgs e)
{
//TextChanged -= DoubleNumericBox_TextChanged;
Text = String.Format("{0:###,###,##0.0###}", Value);
//TextChanged += DoubleNumericBox_TextChanged;
}
private bool IsEntryDisallowed(object sender, string text)
{
var regex = new Regex(#"^[0-9]|\.|\,$");
if (regex.IsMatch(text))
{
return !CheckPontuation(sender, text);
}
//Not a number or a Comma/Point.
return true;
}
private bool IsTextDisallowed(string text)
{
var regex = new Regex(#"^((\d+)|(\d{1,3}(\.\d{3})+)|(\d{1,3}(\.\d{3})(\,\d{3})+))((\,\d{4})|(\,\d{3})|(\,\d{2})|(\,\d{1})|(\,))?$");
return !regex.IsMatch(text); //\d+(?:,\d{1,2})?
}
private bool CheckPontuation(object sender, string next)
{
var textBox = sender as TextBox;
if (textBox == null) return true;
if (Char.IsNumber(next.ToCharArray()[0]))
return true;
if (next.Equals("."))
{
var textAux = textBox.Text;
if (!String.IsNullOrEmpty(textBox.SelectedText))
textAux = textAux.Replace(textBox.SelectedText, "");
//Check if the user can add a point mark here.
var before = textAux.Substring(0, textBox.SelectionStart);
var after = textAux.Substring(textBox.SelectionStart);
//If no text, return true.
if (String.IsNullOrEmpty(before) && String.IsNullOrEmpty(after)) return true;
if (!String.IsNullOrEmpty(before))
{
if (before.Contains(',')) return false;
if (after.Contains("."))
{
var split = before.Split('.');
if (split.Last().Length != 3) return false;
}
}
if (!String.IsNullOrEmpty(after))
{
var split = after.Split('.', ',');
if (split.First().Length != 3) return false;
}
return true;
}
//Only one comma.
if (next.Equals(","))
{
return !textBox.Text.Any(x => x.Equals(','));
}
return true;
}
}

Binding BindableApplicationBarIconButton IsEnabled property to Relaycommand CanExecute Windows Phone 7.1

I am using Phone7.Fx R1
The following works. The system does not react when a user presses the button. This means, than there is no reaction if Stop Game is pressed without a game has been started and vice versa.
However the button looks active. I am aware that I can bind the IsEnabled to a different property, but I would like it to bind to NewGameCanExecute and StopGameCanExecute. Is this possible?
Some XAML code:
<Preview:BindableApplicationBarIconButton Command="{Binding NewGame}" IconUri="/images/icons/appbar.add.rest.png" Text="New game" />
<Preview:BindableApplicationBarIconButton Command="{Binding StopGame}" IconUri="/images/icons/appbar.stop.rest.png" Text="Stop game" />
And the relay commands:
public RelayCommand NewGame { get; private set; }
public RelayCommand StopGame { get; private set; }
//Constructor
NewGame = new RelayCommand(NewGameExecute, NewGameCanExecute);
StopGame = new RelayCommand(StopGameExecute, StopGameCanExecute);
void NewGameExecute()
{
_gameStarted = true;
_gameControlModel.StartNewGame();
StopGame.RaiseCanExecuteChanged();
}
bool NewGameCanExecute()
{
return !_gameStarted;
}
void StopGameExecute()
{
_gameControlModel.StopGame();
_gameStarted = false;
NewGame.RaiseCanExecuteChanged();
}
bool StopGameCanExecute()
{
return _gameStarted;
}
Couple of questions and answers:
Q: Have you tried to set a breakpoint in the CanExecute functions to see if it gets called?
A: Yes. It does get called, but the icon is not grayed out, eventhough false is returned.
The Execute method is not executed, if the CanExecute method returns false. But I want the icon to be grayed out like a normal button.
SOLUTION
I spend some time and came up with a solution, which can be found here:
http://pastebin.com/MM75xACj
This is obviously a bug in whatever BindableApplicationBarIconButton implementation you're using.
Ask the author of it for help, or debug your 3rd party software yourself.
SOLUTION
I spend some time and came up with a solution and edited the applicationbariconbutton class.
namespace Phone7.Fx.Controls
{
public class BindableApplicationBarIconButton : FrameworkElement, IApplicationBarIconButton
{
public int Index { get; set; }
public static DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(BindableApplicationBarIconButton), new PropertyMetadata(null, OnCommandPropertyChanged));
private static void OnCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
((BindableApplicationBarIconButton)d).Command = (ICommand)e.NewValue;
}
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set {
Command.CanExecuteChanged -= ValueOnCanExecuteChanged;
SetValue(CommandProperty, value);
Command.CanExecuteChanged += ValueOnCanExecuteChanged;
}
}
private void ValueOnCanExecuteChanged(object sender, EventArgs eventArgs)
{
ICommand commandSender = sender as ICommand;
if(commandSender != null)
{IsEnabled = commandSender.CanExecute(null);}
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(BindableApplicationBarIconButton), new PropertyMetadata(null, OnCommandParameterPropertyChanged));
private static void OnCommandParameterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var invokeCommand = d as BindableApplicationBarIconButton;
if (invokeCommand != null)
{
invokeCommand.SetValue(CommandParameterProperty, e.NewValue);
}
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set
{
SetValue(CommandParameterProperty, value);
}
}
public static readonly DependencyProperty CommandParameterValueProperty =
DependencyProperty.RegisterAttached("CommandParameterValue", typeof(object), typeof(BindableApplicationBarIconButton), null);
public object CommandParameterValue
{
get
{
var returnValue = GetValue(CommandParameterValueProperty);
return returnValue;
}
set { SetValue(CommandParameterValueProperty, value); }
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(BindableApplicationBarIconButton), new PropertyMetadata(true, OnEnabledChanged));
private static void OnEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
((BindableApplicationBarIconButton)d).Button.IsEnabled = (bool)e.NewValue;
}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.RegisterAttached("Text", typeof(string), typeof(BindableApplicationBarIconButton), new PropertyMetadata(OnTextChanged));
public new static readonly DependencyProperty VisibilityProperty =
DependencyProperty.RegisterAttached("Visibility", typeof(Visibility), typeof(BindableApplicationBarIconButton), new PropertyMetadata(OnVisibilityChanged));
private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
var button = ((BindableApplicationBarIconButton)d);
BindableApplicationBar bar = button.Parent as BindableApplicationBar;
bar.Invalidate();
}
}
public new Visibility Visibility
{
get { return (Visibility)GetValue(VisibilityProperty); }
set { SetValue(VisibilityProperty, value); }
}
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
((BindableApplicationBarIconButton)d).Button.Text = e.NewValue.ToString();
}
}
public ApplicationBarIconButton Button { get; set; }
public BindableApplicationBarIconButton()
{
Button = new ApplicationBarIconButton();
Button.Text = "Text";
Button.Click += ApplicationBarIconButtonClick;
}
void ApplicationBarIconButtonClick(object sender, EventArgs e)
{
if (Command != null && CommandParameter != null)
Command.Execute(CommandParameter);
else if (Command != null)
Command.Execute(CommandParameterValue);
if (Click != null)
Click(this, e);
}
public bool IsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public event EventHandler Click;
public Uri IconUri
{
get { return Button.IconUri; }
set { Button.IconUri = value; }
}
}

Binding to DependencyProperty of a UserControl

I Create a TimeInput Control Like to Enter Time.
<TextBox Text="{Binding Path=Hours}" />
<TextBox IsReadOnly="True"
Focusable="False"
Text=":" />
<TextBox Text="{Binding Path=Minutes}" />
and
public int Hours {
get { return (int)this.GetValue(HoursProperty); }
set {
this.SetValue(HoursProperty, value);
this.OnPropertyChanged("Hours");
}
}
public static readonly DependencyProperty HoursProperty =
DependencyProperty.Register("Hours", typeof(int), typeof(UserControl1), new UIPropertyMetadata(0, new PropertyChangedCallback(OnHoursChanged)));
private static void OnHoursChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
if (obj != null) {
int newValue = (int)e.NewValue;
}
}
public int Minutes {
get { return (int)this.GetValue(MinutesProperty); }
set {
this.SetValue(MinutesProperty, value);
this.OnPropertyChanged("Minutes");
}
}
// Using a DependencyProperty as the backing store for Minutes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MinutesProperty =
DependencyProperty.Register("Minutes", typeof(int), typeof(UserControl1), new UIPropertyMetadata(0, new PropertyChangedCallback(OnMinutesChanged)));
private static void OnMinutesChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
if (obj != null) {
int newValue = (int)e.NewValue;
}
}
public Nullable<TimeSpan> Value {
get { return (Nullable<TimeSpan>)this.GetValue(ValueProperty); }
set {
this.SetValue(ValueProperty, value);
this.OnPropertyChanged("Value");
}
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(Nullable<TimeSpan>), typeof(UserControl1), new UIPropertyMetadata(null, new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
if (obj != null) {
(obj as UserControl1).UpdateTime(e.NewValue as TimeSpan?);
}
}
public void UpdateTime(TimeSpan? newTimeSpan) {
if (newTimeSpan.HasValue) {
this.Hours = newTimeSpan.Value.Hours;
this.Minutes = newTimeSpan.Value.Minutes;
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) {
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
While I use this on another UserControl and Bind to a Property It doesn't work and show values.
I use it like this:
<uc:UserControl1 Value="{Binding StartTime}"/>
and
public TimeSpan StartTime
{
get { return new Types.Time(Item.StartTime).ToTimeSpan(); }
set { Item.StartTime = new Types.Time(value).ToShort(); NotifyPropertyChanged("StartTime"); }
}
That Item is an entity read from database and bind and StartTime is short form of hhmm.
i have updated your code, with dependency properties you don't need fire the property changed event explicit.
public partial class UserControl1 : UserControl
{
public UserControl1() {
this.InitializeComponent();
}
public int Hours {
get { return (int)this.GetValue(HoursProperty); }
set { this.SetValue(HoursProperty, value); }
}
public static readonly DependencyProperty HoursProperty =
DependencyProperty.Register("Hours", typeof(int), typeof(UserControl1), new UIPropertyMetadata(0, new PropertyChangedCallback(OnHoursChanged)));
private static void OnHoursChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
var uc = obj as UserControl1;
if (uc != null && e.NewValue != e.OldValue) {
int newValue = (int)e.NewValue;
uc.TimeValue = new TimeSpan(newValue, uc.Minutes, 0);
}
}
public int Minutes {
get { return (int)this.GetValue(MinutesProperty); }
set { this.SetValue(MinutesProperty, value); }
}
// Using a DependencyProperty as the backing store for Minutes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MinutesProperty =
DependencyProperty.Register("Minutes", typeof(int), typeof(UserControl1), new UIPropertyMetadata(0, new PropertyChangedCallback(OnMinutesChanged)));
private static void OnMinutesChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
var uc = obj as UserControl1;
if (uc != null && e.NewValue != e.OldValue) {
int newValue = (int)e.NewValue;
uc.TimeValue = new TimeSpan(uc.Hours, newValue, 0);
}
}
public Nullable<TimeSpan> TimeValue {
get { return (Nullable<TimeSpan>)this.GetValue(ValueProperty); }
set { this.SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("TimeValue", typeof(Nullable<TimeSpan>), typeof(UserControl1), new UIPropertyMetadata(null, new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
var uc = obj as UserControl1;
if (uc != null && e.NewValue != e.OldValue) {
uc.UpdateTime(e.NewValue as TimeSpan?);
}
}
public void UpdateTime(TimeSpan? newTimeSpan) {
if (newTimeSpan.HasValue) {
this.Hours = newTimeSpan.Value.Hours;
this.Minutes = newTimeSpan.Value.Minutes;
}
}
}
second, i think you use the StartTime property incorrect, use it as dependency property too, or implement INotifyPropertyChanged.
{
// .....
StartTime = new Types.Time(this.Item.StartTime).ToTimeSpan();
// .....
}
public static readonly DependencyProperty StartTimeProperty =
DependencyProperty.Register("StartTime", typeof(TimeSpan?), typeof(Window1), new PropertyMetadata(default(TimeSpan?), new PropertyChangedCallback(OnStartTimePropertyChanged)));
private static void OnStartTimePropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) {
if(e.NewValue != e.OldValue) {
(dependencyObject as Window1).Item.StartTime = new Types.Time(e.NewValue).ToShort();
}
}
public TimeSpan? StartTime {
get { return (TimeSpan?)GetValue(StartTimeProperty); }
set { SetValue(StartTimeProperty, value); }
}
hope this helps
You should not have any other code then calling GetValue and SetValue inside getter and setter of dependency property. But this may not resolve you problem. If you want to call some code when value change then do that inside callback method instead of setter.

WPF- Why can't my custom textbox be selected?

I have this custom textbox that I am working on and I can use it in xaml, but when I run my app I cannot select it or type in it. Here is my code:
public class ModdedTextBox : TextBox
{
private bool selectionStartChangeFromUI;
private bool selectionLengthChangeFromUI;
private bool selectedTextChangeFromUI;
static ModdedTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ModdedTextBox), new FrameworkPropertyMetadata(typeof(ModdedTextBox)));
//this.SelectionChanged += this.OnSelectionChanged;
//PropertyDescriptor VerticalOffsetProperty = TypeDescriptor.GetProperties(typeof(ModdedTextBox))["VerticalOffset"];
//VerticalOffsetProperty.AddValueChanged(this, this.OnVerticalOffsetChanged);
}
public static readonly DependencyProperty BindableSelectionStartProperty =
DependencyProperty.Register(
"BindableSelectionStart",
typeof(int),
typeof(ModdedTextBox),
new PropertyMetadata(OnBindableSelectionStartChanged));
public static readonly DependencyProperty BindableSelectionLengthProperty =
DependencyProperty.Register(
"BindableSelectionLength",
typeof(int),
typeof(ModdedTextBox),
new PropertyMetadata(OnBindableSelectionLengthChanged));
public static readonly DependencyProperty BindableSelectedTextProperty =
DependencyProperty.Register(
"BindableSelectedText",
typeof(string),
typeof(ModdedTextBox),
new PropertyMetadata(OnBindableSelectedTextChanged));
public static readonly DependencyProperty DelayedTextProperty =
DependencyProperty.Register(
"DelayedText",
typeof(string),
typeof(ModdedTextBox),
new PropertyMetadata(OnDelayedTextChanged));
public int BindableSelectionStart
{
get
{
return (int)this.GetValue(BindableSelectionStartProperty);
}
set
{
this.SetValue(BindableSelectionStartProperty, value);
}
}
public int BindableSelectionLength
{
get
{
return (int)this.GetValue(BindableSelectionLengthProperty);
}
set
{
this.SetValue(BindableSelectionLengthProperty, value);
}
}
public string BindableSelectedText
{
get
{
return (string)this.GetValue(BindableSelectedTextProperty);
}
private set
{
this.SetValue(BindableSelectedTextProperty, value);
}
}
public string DelayedText
{
get
{
return (string)this.GetValue(DelayedTextProperty);
}
private set
{
this.SetValue(DelayedTextProperty, value);
}
}
private static void OnBindableSelectionStartChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as ModdedTextBox;
if (!textBox.selectionStartChangeFromUI)
{
int newValue = (int)args.NewValue;
textBox.SelectionStart = newValue;
}
else
{
textBox.selectionStartChangeFromUI = false;
}
}
private static void OnBindableSelectionLengthChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as ModdedTextBox;
if (!textBox.selectionLengthChangeFromUI)
{
int newValue = (int)args.NewValue;
textBox.SelectionLength = newValue;
}
else
{
textBox.selectionLengthChangeFromUI = false;
}
}
private static void OnBindableSelectedTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as ModdedTextBox;
if (!textBox.selectedTextChangeFromUI)
{
string newValue = (string)args.NewValue;
textBox.BindableSelectedText = newValue;
}
else
{
textBox.selectedTextChangeFromUI = false;
}
}
private static void OnDelayedTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
}
private void OnSelectionChanged(object sender, RoutedEventArgs e)
{
if (this.BindableSelectionStart != this.SelectionStart)
{
this.selectionStartChangeFromUI = true;
this.BindableSelectionStart = this.SelectionStart;
}
if (this.BindableSelectionLength != this.SelectionLength)
{
this.selectionLengthChangeFromUI = true;
this.BindableSelectionLength = this.SelectionLength;
}
if (this.BindableSelectedText != this.SelectedText)
{
this.selectedTextChangeFromUI = true;
this.BindableSelectedText = this.SelectedText;
}
}
private void OnVerticalOffsetChanged(object sender, EventArgs e)
{
MessageBox.Show("hello the vertical offset works");
}
}
your control needs a style to display itself.
comment out this one line from the constructor, to use default style
DefaultStyleKeyProperty.OverrideMetadata(typeof(ModdedTextBox), new FrameworkPropertyMetadata(typeof(ModdedTextBox)));
Done!
edit: Alternatively this will explicitly make your control use TextBox style
DefaultStyleKeyProperty.OverrideMetadata(
typeof(ModdedTextBox),
new FrameworkPropertyMetadata(typeof(TextBox)));

Resources