views:

42

answers:

1

I have two instances of a File Browser user control, the control is just a button and a text box, but I need two instances, think a diff utility. Once the user has selected a file for each control I want to enable a button which will perform an action on both of the files.

The problem I am having is how to distinguish between the instances of the controls in order to determine that both files have been selected. I think I would like for my DoSumthinViewModel to only have string properties which the FileChooserViewModel fulfills.

At first I had a single ViewModelLocator with a property which returned a new instance of a the FileChooserVM when accessed, but this just didn't seem right and I could not distinguish between the instances. I then went down the path of a separate Locator for the FileChooser but realized that each control would be talking to the same locator instance and thus the same FileChooserViewModel again.

So, what would be a good technique for working with individual instances of the same ViewModel?

Thanks,

Shane Holder

A: 

Your DoSomethingViewModel could have two properties of type FileChooserViewModel that your controls are bound to, then check their string properties for a value.

A simplified version of your FileChooserViewModel could be...

public class FileChooserViewModel : ViewModelBase
{
    public const string FilePathPropertyName = "FilePath";
    private string _filePath;
    public string FilePath
    {
        get { return _filePath; }
        set
        {
            if (_filePath == value) return;
            _filePath = value;
            RaisePropertyChanged(FilePathPropertyName);
            Messenger.Default.Send(new NotificationMessage("FilePath Updated"));
        }
    }
}

And your DoSomethingViewModel might look like this...

public class DoSomethingViewModel : ViewModelBase
{
    public DoSomethingViewModel()
    {
        Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
    }

    public const string FileChooser1PropertyName = "FileChooser1";
    private FileChooserViewModel _fileChooser1 = new FileChooserViewModel();
    public FileChooserViewModel FileChooser1
    {
        get { return _fileChooser1; }
        set
        {
            if (_fileChooser1 == value) return;
            _fileChooser1 = value;
            RaisePropertyChanged(FileChooser1PropertyName);
        }
    }

    public const string FileChooser2PropertyName = "FileChooser2";
    private FileChooserViewModel _fileChooser2 = new FileChooserViewModel();
    public FileChooserViewModel FileChooser2
    {
        get { return _fileChooser2; }
        set
        {
            if (_fileChooser2 == value) return;
            _fileChooser2 = value;
            RaisePropertyChanged(FileChooser2PropertyName);
        }
    }

    public const string BothFilesChosenPropertyName = "BothFilesChosen";
    public bool BothFilesChosen
    {
        get
        {
            var result = false;
            if (FileChooser1 != null && FileChooser2 != null)
                result = !string.IsNullOrWhiteSpace(FileChooser1.FilePath)
                      && !string.IsNullOrWhiteSpace(FileChooser2.FilePath);
            return result;
        }
    }

    private void NotificationMessageReceived(NotificationMessage msg)
    {
        if (msg.Sender is FileChooserViewModel)
            RaisePropertyChanged(BothFilesChosenPropertyName);
    }
}

The NotificationMessageReceived method is called with a NotificationMessage is sent from FileChooserViewModel's FilePath property setter, and it in turn raises the property changed event on the BothFilesChosen property.

<UserControl x:Class="DoSomethingProject.Views.DoSomethingView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:views="clr-namespace:DoSomethingProject.Views"
             DataContext="{Binding DoSomethingViewModel, Source={StaticResource Locator}}">
    <StackPanel>
        <views:FileChooser DataContext="{Binding Path=FileChooser1}" />
        <views:FileChooser DataContext="{Binding Path=FileChooser2}" />
        <Button IsEnabled="{Binding Path=BothFilesChosen}" />
    </StackPanel>
</UserControl>

Another way to do this would be to handle the PropertyChanged event on each FileChooserViewModel property, but I prefer using messaging because event handling means you need to make sure you un-handle the events, and this can get messy leading to memory issues when events are missed.

Matt Casto
That's close to where I am now, however I believe that the FileChooser[1,2] properties change events won't get fired, because they would never get set correct? The problem I am having is still distinguishing between notifications of FileChooserViewModel.FilePath.
ShaneH
You're totally right. My example didn't properly update the BothFilesChosen property. I changed my examples to use messaging.
Matt Casto
That got it, I did not realize that the property notification had a sender on it, so I was able to use that to distinguish between chooser1 and chooser2. I had taken to putting a Token object on the FileChooser and using that to send a specific message, it seemed to make my FileChooserViewModel smelly but would that be better than receiving every string changed notification?
ShaneH
You could also create your own message class inheriting from MessageBase. In fact, that's what I do in practice. That way you can register with something like Messenger.Default.Register<MyCustomMessage>(this, MyCustomMessageReceived).
Matt Casto
If you add your own message, do you put both the Send<> and the RaisePropertyChanged in the set for the property?
ShaneH
Are you talking about the FilePath property? If so, then yes. You'll still want the FileChooserViewModel to raise the PropertyChanged event when the FilePath value changes.
Matt Casto
Yep, that's what I was talking about. Thanks for your help.
ShaneH