0

I have a control that is set up as a DataTemplate:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<BooleanToVisibilityConverter x:Key="BoolToVis" />
<DataTemplate x:Key="KEYBOARD_EN">
     <StackPanel>
               <Button Visibility="{Binding Path=RegisterButtonVisible}"  Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>

    </StackPanel>        
</DataTemplate>

In this DataTemplate there is a control on which I wish to set the Visibility from various view models:

  <Button Visibility="{Binding Path=RegisterButtonVisible}"  Style="{StaticResource ...} > Register </Button>

I do routed events with my control, so I tried to set up something similar, but no matter what I try, the RegisterButtonVisible property does not get picked up:

public partial class MainKeyboard : UserControl
{
    public static DependencyProperty RegisterButtonVisibleProperty;
    public Visibility RegisterButtonVisible
    {
        get { return (Visibility)GetValue(RegisterButtonVisibleProperty); }
        set { SetValue(RegisterButtonVisibleProperty, value); }
    }

      static MainKeyboard()
    {
        RegisterButtonVisibleProperty = DependencyProperty.Register("RegisterButtonVisible", typeof (Visibility),
            typeof (MainKeyboard));
    }       
 }

In my ViewModel I do this:

    public Visibility RegisterButtonVisible // get, set, raisepropchange, etc

My DataTemplate with the button in it is wrapped in a userControl:

<UserControl x:Class="Bleh.Assets.MainKeyboard"
         x:Name="TheControl"
         Unloaded="UserControl_Unloaded">

<Viewbox>
    <Grid>
        <ContentControl Name="ctrlContent" Button.Click="Grid_Click" />
    </Grid>
</Viewbox>

and is used in my views like this:

  <assets:MainKeyboard 
      RegisterButtonVisible="Collapsed"
                                 Loaded="MainKeyboard_Loaded">
                <b:Interaction.Triggers>
                    <b:EventTrigger EventName="Register">
                        <b:InvokeCommandAction Command="{Binding ConfirmEmailAddressCommand}"/>
                    </b:EventTrigger>
                    <b:EventTrigger EventName="Enter">
                        <b:InvokeCommandAction Command="{Binding EnterKeyCommand}"/>
                    </b:EventTrigger>
                </b:Interaction.Triggers>
            </assets:MainKeyboard>

Please note this attribute:

 RegisterButtonVisible="Collapsed" 

This is my dependency property. It shows up in intelliesense, so the CLR has registered it correctly, but it does NOT pick up the property assignment (Collapsed is ignored).

This makes me feel like it is very close, but I do remember someone telling me I can not do this, thus the EventTriggers (this is a common issue with datatemplates and MVVM apparently).

So one option is to use something in the Interaction namespace, like I do my event triggers ( I just need to fire a "Visibility" trigger on this button somehow, at least I figure).

What is the right ANY way to do this in MVVM?

Taterhead
  • 5,763
  • 4
  • 31
  • 40
Shawn J. Molloy
  • 2,457
  • 5
  • 41
  • 59
  • Don't get hung up on right/wrong...it really comes down to is it easier to maintain going forward. IMHO. I would do the visibility on the VM *as you shown* and trigger it from other properties and put a nice comment in the code to that effect. – ΩmegaMan Mar 24 '16 at 22:28
  • I agree; I am just trying to do things the MVVM way; view first and all that. But I definitely agree with you. – Shawn J. Molloy Mar 24 '16 at 22:46

1 Answers1

0

Fixing your code

In order to make your existing code work, you need to tell need to tell WPF what object RegisterButtonVisible should be read from. If it's a user control, give the UserControl a name and then reference that element via ElementName, like so:

<UserControl ... lots of stuff here 
             x:Name="TheControl"
             >

In your button binding:

<Button Visibility="{Binding ElementName=TheControl, Path=RegisterButtonVisible}"  Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>

Of course, if you can't do that because the button and the usercontrol are in different files, you can still use an ancestor binding:

<Button Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type assets:MainKeyboard}},
                             Path=RegisterButtonVisible}"
        Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>    

which, for each button, will walk up to find the closest instance of assets:MainKeyboard and then bind to the RegisterButtonVisible property.

Using MVVM

If you want to achieve the same using MVVM (instead of on a control), you need to use a converter to convert a boolean to a visibility property, like so:

<Button Visibility="{Binding IsRegistrationAllowed, Converter={StaticResource BoolToVis}}"  Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>

Of course, that assumes that your DataContext is set up correctly and pointing at your ViewModel.

zastrowm
  • 8,017
  • 3
  • 43
  • 63
  • I can not do the latter as my datacontext is not definable; It is IoC and requires mock objects, so there is not constructor that will allow me to set the datacontext. All ViewModels are defined through a data template. But I am trying the former now. – Shawn J. Molloy Mar 24 '16 at 22:49
  • @nocarrier you can set the DataContext in the codebehind as well. So in your Window (or UserControl etc.), you can do `this.DataContext = myViewModelThatWasPassedIntoTheConstructor`. Then you can use the data-context in the WPF code however you'd like. – zastrowm Mar 24 '16 at 22:51
  • Two questions @MackieChan - what will I put in the "TheControl"'s code behind in order to route this property? Simpley specifying ElementName=TheControl doesn't seem to have any effect. Seconldy, Can I just specify a style in the control that host this user control to set the visibility like in this answer: http://stackoverflow.com/a/7001109/589509 – Shawn J. Molloy Mar 25 '16 at 00:05
  • I added some edits with my datatemplate and how it is used in the view (with the UC). – Shawn J. Molloy Mar 25 '16 at 00:24
  • @nocarrier (1st) I added an example of not using a name, but instead using ancestor binding. (2nd) yes you could define a dynamic style that also used an ancestor binding. – zastrowm Mar 28 '16 at 05:10