4

I have several Tiles (TileLayoutControl Class) in my xaml (only shown 2 in this example) whose Visibility are binded to Boolean Properties and converted through BooleanToVisibilityConverter.
This works just fine. My question is

Can I bind the visibility to the Command instead so that I can remove the need of those several Boolean Properties?

Something like binding the Visibility to Command.CanExecute

If Yes, How can I achieve that? Any help will be really appreciated! Thanks.

<dxlc:Tile Command="{Binding Tile1Command}"
           Visibility="{Binding Path=IsTile1Visible , Converter={StaticResource BooleanToVisibilityConverter}}"/>
<dxlc:Tile Command="{Binding Tile2Command}"
           Visibility="{Binding Path=IsTile2Visible , Converter={StaticResource BooleanToVisibilityConverter}}"/>

ViewModel

private bool _isTile1Visible;
public bool IsTile1Visible
{
    get { return _isTile1Visible; }
    set { this.RaiseAndSetIfChanged(ref _isTile1Visible, value); }
}

public ReactiveCommand Tile1Command { get; private set; }

Tile1Command = new ReactiveCommand();
Tile1Command.Subscribe(p => PerformTile1Operation());
Yael
  • 1,566
  • 3
  • 18
  • 25
Butters
  • 947
  • 5
  • 16
  • 25

3 Answers3

6

Yes, just use RxUI bindings:

<dxlc:Tile x:Name="Tile1" />

Then in your View constructor (make sure to implement IViewFor<Tile1ViewModel> to get this extension):

this.BindCommand(ViewModel, x => x.Tile1Command);

this.WhenAnyObservable(x => x.ViewModel.Tile1Command.CanExecuteObservable)
    .BindTo(this, x => x.Tile1.Visibility);

You could also solve this in the ViewModel level, though that's not what I would do - in the ViewModel ctor:

Tile1Command = new ReactiveCommand(/* ... */);
Tile1Command
    .Select(x => x ? Visibility.Visible : Visibility.Collapsed)
    .ToProperty(this, x => x.Tile1Visibility, out tile1Visibility);
Ana Betts
  • 73,868
  • 16
  • 141
  • 209
  • I think the "not what I would do" relates to whether you like writing code (for binding) in the view - conceptually I prefer binding in markup but then again maybe I need to learn more new tricks – Murph May 05 '14 at 07:15
  • 1
    I've worked out what bothers me, one of the nice things about bindings in the markup is that you don't have to give the XAML elements names. Colour me strange but I think that's a good thing. – Murph May 06 '14 at 07:50
2

ReactiveCommand is an ICommand implementation that is simultaneously a RelayCommand implementation...

Assume that the ReactiveCommand has been declared like this...

public ReactiveCommand FileCommand { get; private set; }

...and has been instantiated in a View Model like this...

SomeText = "";
FileCommand = new ReactiveCommand(this.WhenAny(vm => vm.SomeText, s => !string.IsNullOrWhiteSpace(s.Value)));
FileCommand.Subscribe(param => MessageBox.Show("Processing"));

... which means if the property SomeText is empty, then the command cannot be executed, otherwise the command can be executed. And if the command is executed, a message box will get displayed.

If your objective is to simply eliminate the boolean IsTile1Visible, you can make a Xaml declaration like this...

<Button Content="File" 
        Command="{Binding FileCommand}"
        Visibility="{Binding FileCommand, Converter={genericMvvm1:CommandToVisibilityConverter}}" />

where the visibility is bound to the same command and uses a value converter...

and the value converter looks like this...

public class CommandToVisibilityConverter : MarkupExtension, IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        try
        {
            ICommand iCommand = value as ICommand;
            if (iCommand != null)
            {
                if (iCommand.CanExecute(parameter))
                {
                    return Visibility.Visible;
                }
                return Visibility.Collapsed;
            }
        }
        catch
        {
        }
        return value;
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}

The value converter simply dereferences the command into a basic ICommand and converts it into a visibility. Note that since this converter inherits from Markup Extension, there's no need to declare it as a static resource in the Xaml's object graph.

NOTE: the same functionality can be achieved by using 'code-behind' available in ReactiveUI, but the Xaml/ValueConverter appeals to developers who do not want their View Models to deal explicitly with the 'Visibility' property.

Yael
  • 1,566
  • 3
  • 18
  • 25
Gayot Fow
  • 8,710
  • 1
  • 35
  • 48
  • It certainly works (i.e. doesn't fail), but it's not the Right Way To Do Things™ - SO is a source of *Documentation*, I want people to know how to do things correctly with ReactiveUI. – Ana Betts May 01 '14 at 18:44
  • @PaulBetts, can you clarify what you mean by the "Right Way to do Things"? – Gayot Fow May 01 '14 at 20:54
  • 1
    I like convertors - they are part of the broader XAML patterns - that said @PaulBetts shows a way to surface a visibility property in the ViewModel as well as to do so through code based binding. Interesting. – Murph May 05 '14 at 07:24
  • @Murph, in this case the converter is more extensible than code-behind, and Rx is certainly more robust and flexible than Paul would have you believe. There are MANY ways to do things! :) – Gayot Fow May 05 '14 at 14:16
  • In this code, how does the converter get called again when the CanExecute expression is re-evaluated? I'm asking this because it's not working with a `RoutedUICommand`. – Nicke Manarin Jul 19 '20 at 19:37
1

You could potentially do that, but it would require subclassing the command so that it also implements INotifyPropertyChanged, and the underlying condition would need to raise PropertyChange for the CanExecute property whenever it changes.

It won't work without that, as ICommand doesn't implement INotifyPropertyChanged - it uses CanExecuteChanged instead.

Note that you could simplify the property, however, by just handling it yourself in the constructor:

// In constructor:
Tile1Command = new ReactiveCommand();
Tile1Command.Subscribe(p => PerformTile1Operation());

IReactiveObject self = this as IReactiveObject;

Tile1Command.CanExecuteChanged += (o,e) => self.RaisePropertyChanged(new PropertyChangedEventArgs("IsTile1Visible"));

Then your property becomes:

// Use command directly here...
public bool IsTile1Visible
{
    get { return Tile1Command.CanExecute; }
}
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373