0
votes

I have a WPF application and I want the Start button control only enabled if they have to have specified a value in the text box for 'Download Path'.

My ViewModel contains a property for my model "ConfigurationSettings" and an ICommand implementation (CommandImp) for the button:

public class MainWindowViewModel : BaseNotifyPropertyChanged
{        
    private ConfigurationSettings _configurationSettings { get; set; }
    public ConfigurationSettings ConfigurationSettings 
    {
        get
        {
            return _configurationSettings;
        }
        set
        {
            if (_configurationSettings != value)
            {
                _configurationSettings = value;
                RaisePropertyChanged("ConfigurationSettings");
            }
        }
    }

    public CommandImp StartCommand { get; set; } // this is an implementation of ICommand

    public MainWindowViewModel()
    {
        StartCommand = new CommandImp(OnStart, CanStart);

        _configurationSettings = new ConfigurationSettings();
        _configurationSettings.PropertyChanged += delegate (object o, 
                PropertyChangedEventArgs args)
        {                
            StartCommand.RaiseCanExecuteChanged(); // break point here is never reached            
        };            
    }

    private bool CanStart()
    {
        if (!String.IsNullOrEmpty(ConfigurationSettings.DownloadPath))
        {
            return true;
        }
        return false;
    }
}

In my XAML I have a Start button and the with Command = "{Binding StartCommand}".

My ConfigurationSettings class just has a string for the DownloadPath which is bound to a textbox in the XAML:

public class ConfigurationSettings : BaseNotifyPropertyChanged
{
    private string _downloadPath { get; set; }
    public string DownloadPath
    {
        get { return _downloadPath; }
        set
        {
            if (_downloadPath != value)
            {
                _downloadPath = value;
                RaisePropertyChanged("DownloadPath"); // break point here IS reached
            }
        }
    }
} 

When the user enters a DownloadPath, I expect it to be triggering the PropertyChanged Event, and running my delegate method defined in the ViewModel constructor.

If I move the Command Button inside the ConfigurationSettings class I can do away with event subscription and just use StartCommand.RaiseCanExecuteChanged() right beneath RaisePropertyChanged("DownloadPath");. But I don't want the ICommand as part of my Model.

How can I trigger CanStart() when one of the properties of ConfigurationSettings changes?

UPDATE: Here is the XAML for the text box binding:

<TextBlock Text="{Binding ConfigurationSettings.DownloadPath, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" TextWrapping="WrapWithOverflow" />

And the button:

<Button Content="Start" Command="{Binding StartCommand}"></Button>

I should note that the bindings are working correctly. When I update the textblock, I can see in the ViewModel that ConfigurationSettings.DownloadPath is correctly being updated.

BaseNotifyPropertyChanged is an implementation of INotifyPropertyChanged like so:

public class BaseNotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void RaisePropertyChanged(string property)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
    }
}

I don't seem to be having any issues with the property changed event. I can put a break point in here and it is hit when I update the DownloadPath text box. It's when I subscribe to this PropertyChanged event in my ViewModel constructor, my delegate method isn't firing.

1
What's the Text Binding in your xaml? Could it be that the Binding is set default and therefore only fires on lost focus? Or instead of me guessing could you post some xaml please?XAMlMAX
Ok, do you set the DataContext for the control in your code or in xaml? Perhaps you have them set in xaml only and the DataContext is not the instance from your MainVM? Hope this makes sense.XAMlMAX
@SeanOB: How is BaseNotifyPropertyChanged implemented?mm8
I set the DataContext for the whole View in the code behind. Right after InitializeComponent() I get all the values from an XML file saved to disk like this: DataContext = (MainWindowViewModel)xmlSerialize.Deserialize(reader);. I'll add the implementation of BaseNotifyPropertyChanged to the question.SeanOB
Can you put that code snippet in your question? and describe how you get the view model. I thought you did it through new MainViewModel();. Have you tried creating view model manually and then test if the CanStart() works?XAMlMAX

1 Answers

0
votes

Hate to answer my own question but the people commenting made me think about restructuring my question - which led me to the answer before needing to make another update.

The solution was to move my event subscription inside the 'set' function for ConfigurationSettings:

private ConfigurationSettings _configurationSettings { get; set; }
public ConfigurationSettings ConfigurationSettings
{
    get
    {
        return _configurationSettings;
    }
    set
    {
        if (_configurationSettings != value)
        {
            _configurationSettings = value;

            _configurationSettings = new Model.ConfigurationSettings();
            _configurationSettings.PropertyChanged += (o, args) =>
            {
                StartCommand.RaiseCanExecuteChanged();
            };

            RaisePropertyChanged("ConfigurationSettings");                   
        }
    }
}

The problem was where I was setting my Data Context which I did not originally suspect was at all the problem. I load the view model from an XML file on disk. And when the application is closed, I overwrite that file with the latest ViewModel.

In the constructor I was reading and setting the DataContext:

public MainWindowView()
{
    InitializeComponent();

    string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
    DataSourcePath = new Uri(System.IO.Path.Combine(appPath, DataFileName));

    if (File.Exists(DataSourcePath.LocalPath))
    {
        XmlReader reader = XmlReader.Create(DataSourcePath.LocalPath);
        DataContext = (MainWindowViewModel)serialize.Deserialize(reader);
        reader.Close();
    }
    else
    {
        WriteDataViewModelToDisk(); // new empty view model written to disk
    }
}

If this was the first time I ran the code, with no pre-existing file, my delegate event handler actually worked. The issue was when this code loaded a pre-existing XML file, it overwrote the ConfigurationSettings property in my view model - thus destroying the event subscription.