none
Binding a Button Click Command in an Adorner to a DelegateCommand in ViewModel - MVVM / Prism

    Question

  • Hi,

    I am currently working on an application developed using WPF and Prism.

    One of the views in the application requires a Popup for which I am using an Adorner.

    I have used sample code shown in the following example for my Adorner.

    http://www.nbdtech.com/Blog/archive/2010/07/12/wpf-adorners-part-4-ndash-simple-and-powerful-system-for.aspx

    Everything works OK, with the exception of being able to close the Popup.

    I have a button and bind the Click.Command to a DelegateCommand in my ViewModel.

    No matter what I seem to do, I cannot get the Command to be executed.

    I am assuming it is probably somethingt to do with Logical / Visual Tree, but I cannot find a way to deal with this.

    I have tried various things in the binding to the command, including Using ElementName, FindAncestor on the view etc, but all to no avail.

    For the time being, I have used a nasty workaround by attaching the OnClick event of the button to a handler in the View's Code Behind and calling the COmmand Execute from within there.

    Any guidance and help would be very much appreciated as this is driving me nuts.

    The snippets of code are as follows:-

    Adorned Control

     

    <Border Name="MyBorder" Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" a:Adorners.Template="{StaticResource PopupTemplate}" 
    a:Adorners.IsVisible
    ="{Binding Path=IsRemoveNamesVisible}"/>

     

     

    Adorner/Popup Template

       <ControlTemplate x:Key="PopupTemplate">
        <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
         <a:AdornedPlaceholder/>
         <Grid Width="{Binding Path=ActualWidth, ElementName=MyBorder}" Height="{Binding Path=ActualHeight, ElementName=MyBorder}" Margin="0">
          <Rectangle Stroke="Black" Fill="Yellow" RadiusX="6" RadiusY="6" Margin="0"/>
          <TextBlock Text="What are you doing?" Margin="5 25 0 0"/>
          <TextBox Margin="5 45 5 0" VerticalAlignment="Top"/>
          <Button Content="Tweet" Margin="5" VerticalAlignment="Bottom" HorizontalAlignment="Right" 
          cal:Click.Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:DialogEditMessageView}}, 
    Path=DataContext.RemoveNamesCancelCommand}
    "/> </Grid> </Grid> </ControlTemplate>

     

     

    ViewModel Code

     

      public DelegateCommand<object> RemoveNamesCancelCommand { get; set; }
    
    ----------------------------------------------------------------
    
       RemoveNamesCancelCommand = new DelegateCommand<object>(RemoveNamesCancelCommandExecute, RemoveNamesCancelCommandCanExecute);
    
    -----------------------------------------------------------------
    
      private void RemoveNamesCancelCommandExecute(object defaultArgument)
      {
       IsRemoveNamesVisible = false;
      }
    
      private static bool RemoveNamesCancelCommandCanExecute(object defaultArgument)
      {
       return true;
      }
    


     


    Wednesday, July 27, 2011 9:21 AM

Answers

  • All sorted now... and boy, do I feel stupid.

    I had actually missed out the [AdornerDecorator] Decorator in the View...

    Added it in and all works perfectly now, including the Command Binding.

    I would have thought nothing would have worked correctly without it, but it did. It was just the bindings which fell over.

    Anyway, Thanks JP for the advice, and your words actually inspired me to the solution, as I started breaking everything down bit by bit following your post.

    Thanks

    Simon

    • Marked as answer by smlemmon Wednesday, July 27, 2011 6:46 PM
    Wednesday, July 27, 2011 6:46 PM

All replies

  • Apart from debugging or knowing what the root cause of the delegate command is, there's absolutely nothing wrong with closing the window in a click event handler in window code behind.  The cheif reason is that you are closing the window anyway, so just do it.  The other reason is that why spend hours and hours and hours trying to figure this out.  Thirdly the delegate command pattern in my mind violate separation of concerns, in that all the action work of the command is pushed back into the ViewModel.  Besides being a boomerang nightmare to maintain and debug. 

    Chances are that you are bumping into something regarding routed events,in particular the event.handled being set to true when in fact it should not be set to true.  Routed event have a stricty heriarchy they follow.  Tracking down an issue in the heirachy can be tough.

    As for DelegateCommands, I personally never use them, rather I create a folder named commands and put all of my commands in the folder.  If the command needs to interact with the GUI layer I use events or actions to call the specific thing to do.  This concept is similar to Prism regions.


    JP
    Wednesday, July 27, 2011 9:51 AM
  • Hi JP,

    Thanks for your reply.

    I do see your point.

    I have to say, I really get on OK with DelegateCommands and everywhere else in the application uses this same methodology, so would be nice to keep things the same.

    It just seems to fail when the command is attached to a control in the Adorner.

    Other commands in the same window outside of the adorner are working perfectly.

    This has now however lead on to another issue.

    In the same adorner/popup, I have a listbox which needs to bind to an observable collection, and this is also not working.

    It seems that none of the bindings to anything in the viewmodel are working inside of the adorner popup.

    I cannot for the life of me understand why this is and still really need help.

    Cheers

     

    Simon


    Wednesday, July 27, 2011 4:55 PM
  • Sometimes, and I've never been able to tie it down I too cannot get bindings to work the way I want them to.  When that happens, I move the Datacontext binding to the control iteself or it's immediate parent.  It's almost like some controls mask the binding; which they are not supposed to do.  Also, I've come to depend exclusively on the properties page of the control.  Each control will has a promptable section (as you know) for setting the datacontext.  If I use the prompter it allows me to do a sanity check on whether or not the designer can see the Viewmodel.  If I can't see it and I know I have either a static reference to it in the resouces section of the Usercontrol, Window or control, then I know there's something going on with the Viewmodel.  Once I get that fixed and can set it from properties windows then I always use the path drop down in that properites window to choose the property.  This then will futher clue me in to ensuring it has been done correctly. 

     

    As far a the lost event.  You can try to set up event handlers in the parent controls to find out who's eating the routed event.  Then when you find it, just set e.handled = false in that control, this may work for you.


    JP
    Wednesday, July 27, 2011 5:49 PM
  • All sorted now... and boy, do I feel stupid.

    I had actually missed out the [AdornerDecorator] Decorator in the View...

    Added it in and all works perfectly now, including the Command Binding.

    I would have thought nothing would have worked correctly without it, but it did. It was just the bindings which fell over.

    Anyway, Thanks JP for the advice, and your words actually inspired me to the solution, as I started breaking everything down bit by bit following your post.

    Thanks

    Simon

    • Marked as answer by smlemmon Wednesday, July 27, 2011 6:46 PM
    Wednesday, July 27, 2011 6:46 PM
  • I woud take Acountic Guitars's advise with a grain of salt as I do not personally agree with the statement made.  You do want to figure out why this is not working as expected.  This is how you learn and grow as a developer.  You will gain a better understanding of the framework you use to develop.  Also, DelegateCommands are fine and I do not recommend the approach of creating separate Command calsses.  I don't want to start a religious fight so I will leave it at that.

    Now to help you solve your problem.  I tried the code you pasted and I was able to get it to work.  The first thing I would do is stop using cal:Click.Command.  Button has a Command property already. Cal:Click.Command was used in the past when SIlverlight didn't support ICommand and is no longer used. 

    This Works for me:

        <ControlTemplate x:Key="PopupTemplate">
          <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
            <local:AdornedPlaceholder/>
            <Grid Width="{Binding Path=ActualWidth, ElementName=MyBorder}" Height="{Binding Path=ActualHeight, ElementName=MyBorder}" Margin="0">
              <Rectangle Stroke="Black" Fill="Yellow" RadiusX="6" RadiusY="6" Margin="0"/>
              <TextBlock Text="What are you doing?" Margin="5 25 0 0"/>
              <TextBox Margin="5 45 5 0" VerticalAlignment="Top"/>
              <Button Content="Tweet" Margin="5" VerticalAlignment="Bottom" HorizontalAlignment="Right" 
              Command="{Binding RemoveNamesCancelCommand}"/>
            </Grid>
          </Grid>
        </ControlTemplate>
    

    <Border Name="MyBorder" Height="150" BorderBrush="Black" local:Adorners.Template="{StaticResource PopupTemplate}" local:Adorners.IsVisible="True"/>
    

    Notice how the Command for the Button simply binds to the command and nothing else is needed.  Just make sure your DataContext is being set property.

    Wednesday, July 27, 2011 7:18 PM
  • Brian lurks in the shadows of previously answered solutions telling people to take what I say with grain of Salt.  He must have a Certificate of some sort on his desk right?  To clarify, I never intended to say that binding issues are not caused by me when I run into them.  That's why I always now use Properties page.  Op was thankful for advice given that's good enough for me.  But your comments are not.

    DelegateCommands violate SOC.


    JP
    Wednesday, July 27, 2011 7:27 PM
  • Hi Brian,

    Thanks for the reply.

    I absolutely agree 100% with you in relation to finding out why something doesnt work. I have been a developer now for twenty years, the last five of which doing .NET, and I am still learning new stuff every day. I hate it when something doesnt work and I cant find out why.

    Anyway, as per my post above, I made a stupid mistake and managed to get it working. I have also replaced the cal:Click.Command as advised and also removed all the unnecessary RelativeResource stuff from within the bindings and all works perfectly now.

    Thanks for all the help. It is very much appreciated.

    Simon

    Wednesday, July 27, 2011 7:30 PM
  • Brian lurks in the shadows of previously answered solutions telling people to take what I say with grain of Salt.  He must have a Certificate of some sort on his desk right?  To clarify, I never intended to say that binding issues are not caused by me when I run into them.  That's why I always now use Properties page.  Op was thankful for advice given that's good enough for me.  But your comments are not.

    RoutedCommands suck and violate SOC.


    JP


    There is no need to get mad about my opinion.  If you disagree, simply defend your position in an intelligent manner.

    Points of discussion:

    1. "why spend hours and hours and hours trying to figure this out"
    2. "Thirdly the delegate command pattern in my mind violate separation of concerns, in that all the action work of the command is pushed back into the ViewModel"

    These are the issues I addressed with no mention of anything else you said.  Also a DelegateCommand is not a RoutedCommand.  Maybe it is possible you misunderstood the difference and made your assumptions based of that.  I agree RoutedCommands are not preferred.

    Maybe instead of getting mad, you should challenge the way you think every now and then.  You might discover new possibilities.

    Wednesday, July 27, 2011 7:45 PM
  • Brain;

    You assumed I was mad.

    Point 1 above: You're right.

    Point 2 above: RelayCommands/DelegateCommands are not always the defacto standard as taught by some of the WPF diciples.  For example a view wants to GetCustomers.  But maybe it's not the only one to GetCustomers and maybe other views don't use same Viewmodel.  If I have a model that produces customers and I want to be able to reuse commands, I simply put them into their own folder and either send and event or call the model directly to GetCustomers.  When the model is done, it posts and event.  It works for me everytime I do it and it lends to reuse. Especially if I only want the command and not the view model. 

    Besides if you go out to the imfamous site on MVVM you'll see the chap diving into more about RelayCommands than the concepts of MVVM.  I can see where sometimes RelayCommands are ok, but I personally don't use or need to use them.  I like all my commands in one-place, one-time.


    JP
    Wednesday, July 27, 2011 8:17 PM
  • HA! Yes, you are right, I did assume. I did enjoy the lurking part.

    This is where my view differs form yours.  I believe a View does not GetCustomers. There is an interaction by the user that requests to GetCustomers.  This request may be in the form a button click that invokes a command.  Commands do not return data, or act on data.  Commands just lets something (maybe a ViewModel) know that an action needs to be performed.  The Execute method of the Command will have code that ideally calls a repository to ask for the customers.  When the repository returns the customers that value is normally assigned to a Property that exists and is specific to the VM.  When the property is set the UI is notified that it needs to display something and refreshes accordingly.  In my world the Repository is reponsible for all CRUD operations and is reusable accross the application.  Delegate/Relay commands allow you to quickly define the methods that will call the repository without the need for spearate class declarations or events to notify ViewModels of state.

    As you can see, in my scenario I never have the same command twice, but rather I would make use of the same Repository calls for data operations.

    Wednesday, July 27, 2011 8:38 PM
  • Yes your description of that design is exactly the same as mine.  The only difference we may have is that I don't usually include any command logic in the VM.  I put all commands into a command folder and bind the view to those commands. 

    As far as the repository, (I know you have a very specific meaning for that), I just create a Model class, that acts on the data (Asynchronously) and then posts events of DataReady back to whomever is listening which in some cases could be more than one viewmodel.

    Have you looked into Async CTP yet?  It's pretty slick.  I used it the other day with EF whereby instead of creating a WCF service, I wrapped the data classes generated by EF in Async methods.  Its behavior to the user is indistinguishable from using aysnc WCF.  But internally there's less plumbing.  I see it as a mini alternative to WCF.


    JP
    Wednesday, July 27, 2011 9:11 PM
  • Hi Simon

     

    Could you clarify what you meant by "I had actually missed out the [AdornerDecorator] Decorator in the View."

    I can't seem to have any bindings working inside the template

     

    Thanks

    Carlos

    Wednesday, August 10, 2011 2:48 AM
  • Hi Carlos,

    Adding an AdornerDecorator into my View (xaml) fixed it for me.

    I just added it outside the border which surrounded the main content of my view and my Command bindings then worked perfectly..

    <AdornerDecorator>
    
     <Border>
    
      Content here...
    
     </Border>
    
    </AdornerDecorator>
    
    

    Hope that makes sense.

    I have to say adorners drive me insane. Everytime I need to use one, my head invariably ends up in my hands.

    Why displaying a popup or something that overlays something else has to be so damned difficult is beyond me...

    Simon


    Wednesday, August 10, 2011 7:10 AM
  • Hi Simon

     

    Thanks for your answer.

    In the meantime I had found this solution:

    http://stackoverflow.com/questions/764682/no-an-adorner-does-not-automagically-take-the-datacontext-of-its-adornedelement

    So basically modified the ControlAdorner's constructor to look like this:

     

    public ControlAdorner(UIElement adornedElement) : base(adornedElement)

            {

                SetBinding(DataContextProperty,

                    new Binding(DataContextProperty.Name)

                    {

                        Mode = BindingMode.OneWay,

                        Source = adornedElement

                    });

            }

     

    Tested it this way and it worked for me, so you don't need to go and add the <ControlAdorner> tag to each view you want to use your adorner in.

    Hope this helps

    Regards

    Carlos

     

    Friday, August 12, 2011 1:20 AM