0

I'm trying to build a generic Command that can access properties from my ViewModel. On my Window are several TextBoxes, each TextBoxhas a Button next to it. When the Button is clicked I show an OpenFileDialog and set the Text of the TextBox to the selected files path. The TextBoxitself has a Binding to a property in the ViewModel. Currently this is implemented with a Command in the ViewModel. The Buttons all call the same Commandbut each Button has a its property CommandParameter set to the TextBox that will receive the filepath. The drawback of this is, that I need to cast the parameter from the Commandexecution to a TextBox and then set its Textproperty. My question is now, if I can't somehow bind my 'Command' to the same property the TextBoxis bound to. Here is what I currently do:

XAML

<TextBox Text="{Binding SettingsPath}" x:Name="txtSettingsPath"></TextBox>
<Button Command="{Binding OpenFile}" 
CommandParameter="{Binding ElementName=txtSettingsPath}">...</Button>

C#

public ICommand OpenFile
{
    get
    {
        bool CanExecuteOpenFileCommand()
        {
            return true;
        }

        CommandHandler GetOpenFileCommand()
        {
            return new CommandHandler((o) =>
            {
                OpenFileDialog ofd = new OpenFileDialog();
                ofd.Multiselect = false;

                if (!string.IsNullOrEmpty(SettingsPath) && File.Exists(settingsPath))
                {
                    ofd.InitialDirectory = Path.GetDirectoryName(SettingsPath);
                }

                if(ofd.ShowDialog() == true)
                {
                    if(o is TextBox txt)
                    {
                        txt.Text = ofd.FileName;
                    }
                }

            }, CanExecuteOpenFileCommand);
        }

        return GetOpenFileCommand();
    }
}

In the XAML I would like to have something like this:

<TextBox Text="{Binding SettingsPath}"></TextBox>
<Button Command="{Binding OpenFile}" 
CommandParameter="{Binding SettingsPath}">...</Button>
5
  • Can you not use the XAML you posted, then just do o = ofd.FileName ? Commented Jun 28, 2017 at 14:43
  • What you want is a small viewmodel, call it SettingsPathSelectorViewModel, that has a Path property and an OpenFile command. Then you'd write a DataTemplate that looks like your last XAML snippet to display it. You could then have your main viewmodel expose a few SettingsPathSelectorViewModels Commented Jun 28, 2017 at 14:44
  • @Milney Unfortunately not. o is just the string in that case, not a reference to the property. Commented Jun 28, 2017 at 14:50
  • @EdPlunkett I'm not sure if I understand you correctly. How do I display the controls? Could you provide a little sample? Commented Jun 28, 2017 at 14:52
  • @RomanoZumbé Working one up Commented Jun 28, 2017 at 14:53

1 Answer 1

1

Here's what I was talking about in comments:

The "little viewmodel". I added a Label property because in my test project, they all looked the same. That doesn't have to be part of this viewmodel.

public class SettingsPathSelectorViewModel : ViewModelBase
{
    #region Label Property
    private String _label = default(String);
    public String Label
    {
        get { return _label; }
        set
        {
            if (value != _label)
            {
                _label = value;
                OnPropertyChanged();
            }
        }
    }
    #endregion Label Property

    #region SettingsPath Property
    private String _settingsPath = null;
    public String SettingsPath
    {
        get { return _settingsPath; }
        set
        {
            if (value != _settingsPath)
            {
                _settingsPath = value;
                OnPropertyChanged();
            }
        }
    }
    #endregion SettingsPath Property


    public ICommand OpenFile
    {
        get
        {
            bool CanExecuteOpenFileCommand()
            {
                return true;
            }

            //  We're no longer using the parameter, since we now have one                 
            //  command per SettingsPath. 
            CommandHandler GetOpenFileCommand()
            {
                return new CommandHandler((o) =>
                {
                    OpenFileDialog ofd = new OpenFileDialog();
                    ofd.Multiselect = false;

                    if (!string.IsNullOrEmpty(SettingsPath) && System.IO.File.Exists(SettingsPath))
                    {
                        ofd.InitialDirectory = System.IO.Path.GetDirectoryName(SettingsPath);
                    }

                    if (ofd.ShowDialog() == true)
                    {
                        SettingsPath = ofd.FileName;
                    }
                }, o => CanExecuteOpenFileCommand());
            }

            return GetOpenFileCommand();
        }
    }
}

A quickie main viewmodel for demo purposes. We'll illustrate two different ways you could expose these things: Either as named properties, or a collection of varying size displayed in an ItemsControl.

public class MainViewModel : ViewModelBase
{
    public SettingsPathSelectorViewModel FirstPath { get; } = new SettingsPathSelectorViewModel() { Label = "First Path" };
    public SettingsPathSelectorViewModel SecondPath { get; } = new SettingsPathSelectorViewModel() { Label = "Second Path" };

    public ObservableCollection<SettingsPathSelectorViewModel> SettingsPaths { get; } = new ObservableCollection<SettingsPathSelectorViewModel>
    {
        new SettingsPathSelectorViewModel() { Label = "First Collection Path" },
        new SettingsPathSelectorViewModel() { Label = "Second Collection Path" },
        new SettingsPathSelectorViewModel() { Label = "Third Collection Path" },
    };
}

XAML:

<Window.Resources>
    <DataTemplate DataType="{x:Type local:SettingsPathSelectorViewModel}">
        <!-- GroupBox and Label are optional -->
        <GroupBox Header="{Binding Label}">
            <StackPanel Orientation="Horizontal">
                <TextBox Text="{Binding SettingsPath}" />
                <Button 
                    Content="..." 
                    Command="{Binding OpenFile}" 
                    HorizontalAlignment="Left" 
                    MinWidth="40" 
                    Margin="4,0,0,0" 
                    />
            </StackPanel>
        </GroupBox>
    </DataTemplate>
</Window.Resources>
<Grid>
    <StackPanel Orientation="Vertical">
        <StackPanel Orientation="Horizontal">
            <ContentControl Content="{Binding FirstPath}" />
            <ContentControl Content="{Binding SecondPath}" />
        </StackPanel>
        <ItemsControl
            ItemsSource="{Binding SettingsPaths}"
            />
    </StackPanel>
</Grid>

Here's what I mean about omitting Label and the GroupBox:

<Window.Resources>
    <DataTemplate DataType="{x:Type local:SettingsPathSelectorViewModel}">
        <StackPanel Orientation="Horizontal">
            <TextBox Text="{Binding SettingsPath}" />
            <Button 
                Content="..." 
                Command="{Binding OpenFile}" 
                HorizontalAlignment="Left" 
                MinWidth="40" 
                Margin="4,0,0,0" 
                />
        </StackPanel>
    </DataTemplate>
</Window.Resources>
<Grid>
    <StackPanel Orientation="Vertical">
        <Label>First Path</Label>
        <ContentControl Content="{Binding FirstPath}" />
        <Label>Second Path</Label>
        <ContentControl Content="{Binding SecondPath}" />
    </StackPanel>
</Grid>
Sign up to request clarification or add additional context in comments.

2 Comments

Wow! That's a cool and clean solution. I'm an absolute WPF (and MVVM) newbie and amazed with the possibilities! Thank you very much!
@RomanoZumbé It gets really fun once you work your way up the learning curve a bit.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.