caliburn.micro bind element visibility to viewmodel's function rather than a property
Asked Answered
Y

2

5

first of all - I'm sorry if it's a duplicate - been looking around for awhile and couldn't find an answer to that,

We're using caliburn.micro so the solution must be using this tool.

We have a view that is consisted of 9 buttons, however - not all of them will be visible in the same time, it depends on events on the system. Each button visibility to either visible or collapsed based on current status but since it is a large number of buttons, and may increase in the future I'd rather have a single function to do this (receiving a name or an enum and returning visibility) rather than having a large amount of properties to bind each button to.

Is it even an option? I couldn't seem to find a way of doing it in any conventional way.

Since the events are being received from outside the software we're developing doing this on the view level is not really an option (or at least - not a right one)

Edit: Here's a snippet of the view that I wish to modify:

            <Grid Margin="0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="120" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>

                <uiviews:PhonePadView Grid.Column="0" x:Name="DestinationDn" cal:Bind.Model="UI.ViewModels.PhonePadViewModel" />

                <Button  Grid.Column="1" Content="Dial" Style="{StaticResource PhonePadBasicFunctionsButtons}" x:Name="MakeCall" Visibility="{Binding btnMakeCallVisibility}" />
                <Button  Grid.Column="1" Content="Answer" Style="{StaticResource PhonePadBasicFunctionsButtons}" x:Name="AnswerCall" Visibility="{Binding btnAnswerCallVisibility}" />
                <Button  Grid.Column="1" Content="Hang-up" Style="{StaticResource PhonePadBasicFunctionsButtons}" x:Name="ReleaseCall" Visibility="{Binding btnReleaseCallVisibility}" />
                <Button  Grid.Column="1" Content="Hold" Style="{StaticResource PhonePadBasicFunctionsButtons}" x:Name="HoldCall" Visibility="{Binding btnHoldCallVisibility}" />

            </Grid>

As you can see, I need to have a different property for each of the buttons, and I refuse to believe this is the only way, I do have a property holding the current status (phone is ringing, in a call, dialing etc.) and it's easy to have a function on the VM to tell which button should be visible and which shouldn't, and on top of it we currently have 9 buttons but it may just as easily expand more, so I'm looking for the most modular code possible here

Yorkshire answered 8/6, 2015 at 16:42 Comment(12)
Did you consider having 9 instances of a (sub-)ViewModel and constructing the corresponding 1-button View?Nervy
Not really... I don't quiet understand your suggestion to be honest - could you elaborate?Yorkshire
I suspect your problem can be solved by dividing it into UserControls with 1 Button. But without seeing any code it's hard to be concrete.Nervy
Of cores that's an option, it's also an option having a property for each button separately, but I wish to try and save on the writing as well as making the code a bit more easy to read, I'll post an edited snipped of the code to make the question clearerYorkshire
There's not much wrong with the posted code, XAML just isn't a compact language. But you ought to bind through a BooleanToVisibility convertor to boolean properties.Nervy
Enable/Disable is easier thanks to Caliburn, just add a CanMakeCall property. Maybe a custom convention will let you do that for the Visibility too, I don't know.Nervy
I agree, but our UX guys wants the buttons to disappear and appear so I'm kind of stumped, My current solution is to have a single hashmap property that'll hold the list of the buttons and have a converter to do the logic for me on the view level, if it'll go well I'll post it as an answer unless someone has a better idea :)Yorkshire
If the button visibility logic is not so complex, why not use a Converter or a MultiValueConverter to decide the visibility? If only ViewModel can decide these based on complex states, then try reducing it into a computed property or two like CurrentVisibleButtonGroup or something and make it an enum or a number. based on that property, decide which buttons should be visible using a simple value converter plugged in between.Arthur
And that's exactly what I meant, I'm creating a hashset<string> on the VM that'll contain the visible buttons names and a converter that'll return "visible" if the string is contained or "collapsed" if not, was just wondering if anyone has a better idea, it sounds like way too much writing to achieve something as simple as binding to a function, which seems to be not supportedYorkshire
WPF follows and recommends a practice, the command pattern for binding actions to buttons, and disabling rather than hiding the buttons, so expect the task to be difficult when you are going against those recommendations.Arthur
@Mathew - Any chance you could give me the link to that? would help me settle an argument I had about it with the boss :)Yorkshire
This should help you get started. And that is why command sources have Command and CommandParameter properties built in, and that includes Button and Menu items.Arthur
J
17

Out of the box, if you name something with the "IsVisible" suffix, Caliburn Micro will toggle visibility.

On the View:

<Grid Name="ConfigEditorIsVisible">
    <TextBlock>Test</TextBlock>
</Grid>

On the ViewModel:

public bool ConfigEditorIsVisible { get; set; }
Josephus answered 21/10, 2015 at 19:27 Comment(1)
It has to be on the Grid or StackPanel (and I guess WrapPanel), not directly on the control, which should then be put inside the container. The answer is right, it's just an aclaration.Bashemeth
Y
0

So... in the end this is what I did: on the VM:

    private HashSet<string> actionsEnabledSet = null;
    public HashSet<string> ActionsEnabledSet
    {
        get { return actionsEnabledSet; }
        set
        {
            actionsEnabledSet = value;
            NotifyOfPropertyChange(() => ActionsEnabledSet);
        }
    }

Alongside changing the hashset based on the items I wish to have visible.

A (multi) converter with the convert function:

    public object Convert(object[] value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
    {
        if (value[0].GetType() == typeof(HashSet<string>))
        {
            if (value[1].GetType() == typeof(string))
            {
                if (((HashSet<string>)value[0]).Contains((string)value[1]))
                    return Visibility.Visible;
            }
        }
        return Visibility.Collapsed;
    }

View button styling: (To avoid duplicate code as much as I could)

    <Style TargetType="Button" x:Key="PhonePadBasicFunctionsButtons" >
        <Setter Property="Margin" Value="0,0,0,0" />
        <Setter Property="Focusable" Value="False" />
        <Setter Property="HorizontalAlignment" Value="Stretch" />
        <Setter Property="Width" Value="75" />
        <Setter Property="Visibility">
            <Setter.Value>
                <MultiBinding Converter="{StaticResource PhoneActionVisibilityConverter1}">
                    <Binding Path="ActionsEnabledSet" Mode="OneWay"/>
                    <Binding RelativeSource="{RelativeSource Self}" Path="Tag" />
                </MultiBinding>
            </Setter.Value>
        </Setter>
    </Style>

And a stackpanel of the buttons that looks like that:

                <Button  Content="Call" Style="{StaticResource PhonePadBasicFunctionsButtons}" x:Name="MakeCall" Tag="MakeCall" cal:Message.Attach="MakeCall" />
                <Button  Content="Answer" Style="{StaticResource PhonePadBasicFunctionsButtons}" x:Name="AnswerCall" Tag="AnswerCall" cal:Message.Attach="AnswerCall" />

Really annoyed I couldn't find a way to bind "tag" to "x:name", but adding new buttons is as easy as adding a single line and it feels better to have the code this way, would love to have any ideas on how to make this better, I think it's elegant but I have a feeling it could be easier to read/implement.

I had to use multibinding as using covnerterproperty is not an option - you cannot bind an element to a property as it seems, hope this'll help to someone in the future - was a nice challange :)

And thank you for everyone who tried to help - you got me out of my box and actually looking for an answer!

Yorkshire answered 10/6, 2015 at 15:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.