Accessing string in ViewModel from IValueConverter

mrw 201 Reputation points
2020-12-01T21:20:39.807+00:00

I am trying to create a converter that will take string property from ViewModel, compare it to time passed (time is inputted to TextBox HoursLimitProp in a format 07:30) and then change Label color accordingly. I know this is bad idea to do this way with

var SVM = new SettingsViewModel();

So what is the right way of accessing property in ViewModel from IValueConverter? I have tried to change SVM.HoursLimitProp to text as it is actually a value, but then I am getting null exception.

TimeExceededConverter.cs:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
using System.Windows.Media;

namespace Activitytracker
{
    class TimeExceededConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string text = (string)value;
            var dateNow = DateTime.Now;

            var SVM = new SettingsViewModel();

            List<int> TimeSplit = SVM.HoursLimitProp.Split(':').Where(x => int.TryParse(x, out _)).Select(int.Parse).ToList();

            DateTime RingTime = new DateTime(dateNow.Year, dateNow.Month, dateNow.Day, TimeSplit[0], TimeSplit[1], 00);

            TimeSpan res;
            var result = TimeSpan.TryParseExact(text, @"hh\:mm\:ss", CultureInfo.InvariantCulture, out res);

            if (res < RingTime.TimeOfDay)
            {
                return Brushes.Green;
            }

            return Brushes.Red;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }

        #endregion
    }
}

SettingsViewModel.cs:

....

    private string _hoursLimit;

    public string HoursLimitProp
    {
        get { return _hoursLimit; }
        set
        {
            if (_hoursLimit != value)
            {
                _hoursLimit = value;
                OnPropertyChanged();
            }
        }
    }

....

MainWindow.xaml:

...

                <Label DataContext="{Binding ViewModel}" Style="{StaticResource TopBarLabel_Style}" x:Name="lblTime" 
                   Content="{Binding Path=CurrentTime}" HorizontalAlignment="Left" Height="33" 
                   Foreground="{Binding Path=CurrentTime, Converter={StaticResource TimeExceededConverter}}"
                   Width="89" Grid.Column="1"/>
...
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,670 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
762 questions
0 comments No comments
{count} votes

Accepted answer
  1. DaisyTian-1203 11,616 Reputation points
    2020-12-03T08:48:06.33+00:00

    The TextBox can't invoke PropertyChanged because of the focus on it. I implement it with pressing the Return key after finishing the inputing. Below is my updated parts for your project:

    Part 1: Add a MyCommand.cs in your ViewModel File.

    class MyCommand : ICommand  
        {  
            private Func<object, bool> _canExecute;  
            private Action<object> _execute;  
      
            public event EventHandler CanExecuteChanged  
            {  
                add  
                {  
                    if (_canExecute != null)  
                    {  
                        CommandManager.RequerySuggested += value;  
                    }  
                }  
                remove  
                {  
                    if (_canExecute != null)  
                    {  
                        CommandManager.RequerySuggested -= value;  
                    }  
                }  
            }  
      
      
            public bool CanExecute(object parameter)  
            {  
                if (_canExecute == null) return true;  
                return _canExecute(parameter);  
            }  
      
            public void Execute(object parameter)  
            {  
                if (_execute != null && CanExecute(parameter))  
                {  
                    _execute(parameter);  
                }  
            }  
      
            public MyCommand(Action<object> execute) : this(execute, null)  
            {  
            }  
            public MyCommand(Action<object> execute, Func<object, bool> canExecute)  
            {  
                _execute = execute;  
                _canExecute = canExecute;  
            }  
        }  
    

    Part 2: Replace your HoursLimitBox TextBox with below code:

     <TextBox x:Name="HoursLimitBox" Text="{Binding HoursLimitProp,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="36" Margin="133,10,0,0"  VerticalAlignment="Top" Width="166" FontSize="20">  
                <TextBox.InputBindings>  
                    <KeyBinding Command="{Binding EnterCommand}" Key="Return"></KeyBinding>  
                </TextBox.InputBindings>  
            </TextBox>  
    

    Part 3: Add below code in ViewModel.cs

     private MyCommand _enterCommand;  
            public MyCommand EnterCommand  
            {  
                get  
                {  
                    if (_enterCommand == null)  
                        _enterCommand = new MyCommand(new Action<object>  
                            (  
                            o =>  
                            {  
                                HoursLimitConfProp = HoursLimitProp;  
                            }  
                            ));  
                    return _enterCommand;  
                }  
            }  
    

    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. DaisyTian-1203 11,616 Reputation points
    2020-12-02T06:13:23.537+00:00

    I do some changes for your code to show the label.
    Part 1 : Add a constructor in SettingsViewModel.cs to give value to

      public SettingsViewModel()  
            {  
                _currentTime = System.DateTime.Now.ToString("hh:mm:ss");  
                _hoursLimit = System.DateTime.Now.AddMinutes(10).ToString("hh: mm:ss");  
            }  
    

    Part 2: The updated code for MainWindow.xaml

     <Window.Resources>  
            <local:TimeExceededConverter x:Key="TimeExceededConverter"></local:TimeExceededConverter>  
        </Window.Resources>  
        <Grid>  
            <Label x:Name="lblTime"  
                   Content="{Binding Path=CurrentTime}"   
                   Foreground="{Binding Path=CurrentTime, Converter={StaticResource TimeExceededConverter}}"  
                   HorizontalAlignment="Left" Height="33"  Width="200">  
                <Label.DataContext>  
                    <local:SettingsViewModel x:Name="ViewModel"></local:SettingsViewModel>  
                </Label.DataContext>  
            </Label>  
        </Grid>  
    

    This is my answer based on my understanding, if I misunderstand, please point out.


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.