Meshka
Meshka

Reputation: 25

How can i use two different DataContexts in a Custom Control in WPF C#?

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:

enter image description here

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:

enter image description here

Upvotes: 0

Views: 59

Answers (0)

Related Questions