Reputation: 25
In my WPF C# .NET 8 Application i have a Custom Control which is a CustomDateTime Control that collects and display partial dates like this:
The issue i am facing now is that the DataContext of the CustomControl is set to this.DataContext = this
in the ctor of the CustomControl, and when i try use the Date
Property like this Date="{Binding DataItems/Date}"
in the XAML i found that nothing is displayed in the control because the DataContext is changed to the ViewModel, and when i set the DataContext to this
the Date
Property will not receive Data.
Now how can i make the Date
Property to use the DataContext provided by the user in Date="{Binding DataItems/Date}"
and the Year
, Month
and Day
Properties to use this
as the DataContext so that they work as displayed in the gif above?
HERE ARE MY CODES:
the Style in Generic.xaml:
<Style TargetType="{x:Type local:CustomDateTime}">
<Setter Property="Background" Value="{DynamicResource MaterialDesignDarkBackground}"/>
<Setter Property="BorderBrush" Value="{DynamicResource MaterialDesignBody}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomDateTime}">
<Grid FlowDirection="RightToLeft">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="dummyTextBox"
Text="{Binding Date, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Foreground="Black"
Grid.Column="0"
TextAlignment="Right"
HorizontalAlignment="Stretch"
Grid.ColumnSpan="3"
materialDesign:HintAssist.Hint="{Binding Path=(materialDesign:HintAssist.Hint),RelativeSource={RelativeSource Mode=TemplatedParent}}"
materialDesign:HintAssist.IsFloating="False"
materialDesign:HintAssist.HintOpacity="0.10"
HorizontalContentAlignment="Right"
IsTabStop="False"
IsHitTestVisible="False"
IsReadOnly="True"
FlowDirection="LeftToRight"/>
<TextBox x:Name="YearTextBox"
Grid.Column="0"
Foreground="Black"
Text="{Binding Year, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
TextAlignment="Left"
HorizontalAlignment="Stretch"
materialDesign:HintAssist.Hint="Year"
materialDesign:TextFieldAssist.RippleOnFocusEnabled="True"
materialDesign:HintAssist.IsFloating="False"
materialDesign:HintAssist.HintOpacity="0.25"
TabIndex="0"
Grid.ColumnSpan="3"
VerticalAlignment="Bottom"
Opacity="0.001"
Margin="10 0 0 0"/>
<TextBox x:Name="MonthTextBox"
IsEnabled="False"
Grid.Column="1"
Text="{Binding Month, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
TextAlignment="Left"
HorizontalAlignment="Stretch"
materialDesign:HintAssist.Hint="Month"
materialDesign:TextFieldAssist.RippleOnFocusEnabled="True"
materialDesign:HintAssist.IsFloating="False"
materialDesign:HintAssist.HintOpacity="0.25"
VerticalAlignment="Bottom"
TabIndex="1"
Visibility="Collapsed"
Margin="10 0 0 0"/>
<TextBox x:Name="DayTextBox"
IsEnabled="False"
Grid.Column="2"
Text="{Binding Day, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
TextAlignment="Left"
HorizontalAlignment="Stretch"
materialDesign:HintAssist.Hint="Day"
materialDesign:TextFieldAssist.RippleOnFocusEnabled="True"
materialDesign:HintAssist.IsFloating="False"
materialDesign:HintAssist.HintOpacity="0.25"
VerticalAlignment="Bottom"
TabIndex="2"
Visibility="Collapsed"
Margin="10 0 0 0"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter TargetName="YearTextBox" Property="Opacity" Value="1"/>
<Setter TargetName="YearTextBox" Property="Grid.ColumnSpan" Value="1"/>
<Setter TargetName="MonthTextBox" Property="Visibility" Value="Visible"/>
<Setter TargetName="DayTextBox" Property="Visibility" Value="Visible"/>
<Setter TargetName="dummyTextBox" Property="Text" Value=" "/>
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="false">
<Setter TargetName="dummyTextBox" Property="Text" Value="{Binding Date, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
CustomDateTime.cs:
using System.Media;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace CustomDateTime
{
public class CustomDateTime : Control
{
private TextBox YearTextBox;
private TextBox MonthTextBox;
private TextBox DayTextBox;
static CustomDateTime()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomDateTime), new FrameworkPropertyMetadata(typeof(CustomDateTime)));
}
public static readonly DependencyProperty MinYearProperty =
DependencyProperty.Register("MinYear", typeof(int), typeof(CustomDateTime), new PropertyMetadata(int.MinValue));
public int MinYear
{
get { return (int)GetValue(MinYearProperty); }
set { SetValue(MinYearProperty, value); }
}
public static readonly DependencyProperty MaxYearProperty =
DependencyProperty.Register("MaxYear", typeof(int), typeof(CustomDateTime), new PropertyMetadata(int.MaxValue));
public int MaxYear
{
get { return (int)GetValue(MaxYearProperty); }
set { SetValue(MaxYearProperty, value); }
}
public static readonly DependencyProperty DateProperty =
DependencyProperty.Register("Date", typeof(string), typeof(CustomDateTime), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnDateChanged));
public string Date
{
get { return (string)GetValue(DateProperty); }
set { SetValue(DateProperty, value); }
}
public static readonly DependencyProperty YearProperty =
DependencyProperty.Register("Year", typeof(string), typeof(CustomDateTime), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnDatePartChanged));
private string Year
{
get { return (string)GetValue(YearProperty); }
set { SetValue(YearProperty, value); }
}
public static readonly DependencyProperty MonthProperty =
DependencyProperty.Register("Month", typeof(string), typeof(CustomDateTime), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnDatePartChanged));
private string Month
{
get { return (string)GetValue(MonthProperty); }
set { SetValue(MonthProperty, value); }
}
public static readonly DependencyProperty DayProperty =
DependencyProperty.Register("Day", typeof(string), typeof(CustomDateTime), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnDatePartChanged));
private string Day
{
get { return (string)GetValue(DayProperty); }
set { SetValue(DayProperty, value); }
}
public CustomDateTime()
{
this.DataContext = this;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Get the TextBox references
YearTextBox = GetTemplateChild("YearTextBox") as TextBox;
MonthTextBox = GetTemplateChild("MonthTextBox") as TextBox;
DayTextBox = GetTemplateChild("DayTextBox") as TextBox;
//if (YearTextBox != null)
//{
// YearTextBox.SetBinding(FrameworkElement.DataContextProperty, new Binding { Source = this, Path = new PropertyPath(Year),
// UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged } );
//}
// Add event handlers to the TextBoxes
if (YearTextBox != null && MonthTextBox != null && DayTextBox != null)
{
YearTextBox.PreviewTextInput += TextBox_PreviewTextInput;
YearTextBox.Loaded += TextBox_Loaded;
YearTextBox.TextChanged += TextBox_TextChanged;
MonthTextBox.PreviewTextInput += TextBox_PreviewTextInput;
MonthTextBox.Loaded += TextBox_Loaded;
MonthTextBox.TextChanged += TextBox_TextChanged;
DayTextBox.PreviewTextInput += TextBox_PreviewTextInput;
}
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
DisableAndEnableTextBoxes(sender as TextBox);
}
private void TextBox_Loaded(object sender, RoutedEventArgs e)
{
DisableAndEnableTextBoxes(sender as TextBox);
}
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
TextBox textBox = sender as TextBox;
// Check if the entered text is not a digit
if (!char.IsDigit(e.Text, 0))
{
e.Handled = true; // Do not allow non-digit characters
return;
}
string newText = textBox.Text.Substring(0, textBox.SelectionStart) + e.Text + textBox.Text.Substring(textBox.SelectionStart + textBox.SelectionLength);
int value;
// Validate the entire text of the TextBox
if (!int.TryParse(newText, out value))
{
e.Handled = true; // Invalid integer format
return;
}
// Check the maximum length based on the TextBox name
if (textBox.Name == "YearTextBox" && newText.Length > 4 ||
textBox.Name == "MonthTextBox" && newText.Length > 2 ||
textBox.Name == "DayTextBox" && newText.Length > 2)
{
e.Handled = true; // Do not allow more characters than the specified length
return;
}
// Validate the input based on the allowed range
if (textBox.Name == "YearTextBox" && (value < MinYear || value > MaxYear) && newText.Length == 4 ||
textBox.Name == "MonthTextBox" && (value < 1 || value > 12) ||
textBox.Name == "DayTextBox" && (value < 1 || value > 30))
{
if (textBox.Name == "YearTextBox")
{
MessageBox.Show($"Year must be between {MinYear} and {MaxYear}");
}
if (textBox.Name == "MonthTextBox")
{
MessageBox.Show($"Month must be between 1 and 12");
}
if (textBox.Name == "DayTextBox")
{
MessageBox.Show($"Day must be between 1 and 30");
}
SystemSounds.Exclamation.Play();
e.Handled = true; // Value is out of range
}
DisableAndEnableTextBoxes(textBox);
}
private static void OnDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomDateTime customDateTime = (CustomDateTime)d;
customDateTime.UpdateDateParts();
}
private static void OnDatePartChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomDateTime customDateTime = (CustomDateTime)d;
customDateTime.UpdateDate();
}
//This method is used to Disable and Enable the TextBoxes based on the value of the Other TextBox
//i.e if the YearTextBox has a valid value then enable the MonthTextBox, and if the MonthTextBox has a valid value then enable the DayTextBox
private void DisableAndEnableTextBoxes(TextBox textBox)
{
if (textBox != null)
{
int value = 0;
if (textBox.Text != "")
{
value = Convert.ToInt32(textBox.Text);
}
if (textBox.Name == "YearTextBox")
{
if (value >= MinYear & value <= MaxYear)
{
MonthTextBox.IsEnabled = true;
}
else
{
MonthTextBox.IsEnabled = false;
}
}
if (textBox.Name == "MonthTextBox" & YearTextBox.IsEnabled == true)
{
if (value >= 1 & value <= 12)
{
DayTextBox.IsEnabled = true;
}
else
{
DayTextBox.IsEnabled = false;
}
}
}
}
private void UpdateDateParts()
{
if (!string.IsNullOrEmpty(Date))
{
string[] parts = Date.Split('/');
int day, month, year;
if (parts.Length == 3)
{
if (int.TryParse(parts[0], out day) && day >= 1 && day <= 30 &&
int.TryParse(parts[1], out month) && month >= 1 && month <= 12 &&
int.TryParse(parts[2], out year) && year >= MinYear && year <= MaxYear)
{
Day = parts[0];
Month = parts[1];
Year = parts[2];
}
}
else if (parts.Length == 2)
{
if (int.TryParse(parts[0], out month) && month >= 1 && month <= 12 &&
int.TryParse(parts[1], out year) && year >= MinYear && year <= MaxYear)
{
Day = string.Empty; // No day provided
Month = parts[0];
Year = parts[1];
}
}
else if (parts.Length == 1)
{
if (int.TryParse(parts[0], out year) && year >= MinYear && year <= MaxYear)
{
Day = string.Empty; // No day provided
Month = string.Empty; // No month provided
Year = parts[0];
}
}
}
else
{
// Clear all parts if Date is empty
Day = string.Empty;
Month = string.Empty;
Year = string.Empty;
}
}
private void UpdateDate()
{
string date = ",,";
if (!string.IsNullOrEmpty(Day))
{
date = date.Insert(0, $"{Day}/");
}
if (!string.IsNullOrEmpty(Month))
{
date = date.Insert(date.IndexOf(',') + 1, $"{Month}/");
}
if (!string.IsNullOrEmpty(Year))
{
date = date.Insert(date.LastIndexOf(',') + 1, Year);
}
string[] parts = date.Split(',');
date = "";
foreach (string part in parts)
{
date += part;
}
Date = date;
}
}
}
MainWindowViewModel.cs:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace CustomDateTime
{
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<DataItem> _dataItems;
public ObservableCollection<DataItem> DataItems
{
get { return _dataItems; }
set
{
_dataItems = value;
OnPropertyChanged();
}
}
public MainWindowViewModel()
{
// Sample data for the grid
DataItems = new ObservableCollection<DataItem>
{
new DataItem { Name = "Item 1", Date = "01/01/2022" },
new DataItem { Name = "Item 2", Date = "05/02/2022" },
new DataItem { Name = "Item 3", Date = "10/03/2022" }
};
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class DataItem : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
private string _date;
public string Date
{
get { return _date; }
set
{
if (_date != value)
{
_date = value;
OnPropertyChanged(nameof(Date));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
usage in XAML:
<local:CustomDateTime Date="{Binding DataItems/Date}"
MinYear="1430"
MaxYear="1445"
/>
Here is the issue displayed as gif:
Upvotes: 0
Views: 59