3

I created a custom confirmation window in an app with Prism Unity, WPF & Mvvm. I need help with the notifications that need to be sent back to the viewmodel. I have this in the detail record view, let's call it MyDetailView.

<!-- Custom Confirmation Window -->
<ie:Interaction.Triggers>
  <interactionRequest:InteractionRequestTrigger 
       SourceObject="{Binding ConfirmationRequest, Mode=TwoWay}">
     <mycontrols:PopupWindowAction1 IsModal="True"/>
  </interactionRequest:InteractionRequestTrigger>
</ie:Interaction.Triggers>

As shown above, I made the interaction Mode=TwoWay so that the confirmation popup window can send back the button click result for the OK or Cancel button. The confirmation window appears as it should, but I don't know how to send the button click result back to my viewmodel, say MyDetailViewModel. That is the main question.

EDIT: This is MyDetailViewMmodel method that raises the InteractionRequest.

private void RaiseConfirmation()
{ConfirmationRequest
    .Raise(new Confirmation()
    {
        Title = "Confirmation Popup",
        Content = "Save Changes?"
    },  c =>{if (c.Confirmed)
{ UoW.AdrTypeRos.Submit();}

This is the PopupWindowAction1 class. Part of the answer to the question may be how do I implement the Notification and FinishedInteraction methods.

class PopupWindowAction1 : PopupWindowAction, IInteractionRequestAware
{
    protected override Window GetWindow(INotification notification)
    { // custom metrowindow using mahapps
        MetroWindow wrapperWindow = new ConfirmWindow1();
        wrapperWindow.DataContext = notification;
        wrapperWindow.Title = notification.Title;

        this.PrepareContentForWindow(notification, wrapperWindow);

        return wrapperWindow;
    }

    public INotification Notification
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }

    public Action FinishInteraction
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }
}

Is there some interaction I need to put in my ConfirmWindow1, something like this?

<i:Interaction.Triggers>
  <i:EventTrigger EventName="PreviewMouseLeftButtonUp">
    <ei:CallMethodAction 
      TargetObject="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, 
        Path=DataContext}"
      MethodName="DataContext.ValidateConfirm"/>
  </i:EventTrigger>
</i:Interaction.Triggers>

Do I even need to do that type of interaction within the button? If so, how do I code it needs so that it corresponds to the particular viewmodel that invoked the interaction. Any suggestions? Thank you.

Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
harpagornis
  • 103
  • 3
  • 17

1 Answers1

7

Main thing is, when you raise the interaction, provide a callback that is triggered when the interaction is finished. This callback gets the notification back and your interaction should have stored all potentially interesting return values there.

Here's an example...

Relevant parts of the ViewModel:

public InteractionRequest<SelectQuantityNotification> SelectQuantityRequest
{
    get;
}

// in some handler that triggers the interaction
SelectQuantityRequest.Raise( new SelectQuantityNotification { Title = "Load how much stuff?", Maximum = maximumQuantity },
    notification =>
    {
        if (notification.Confirmed)
            _worldStateService.ExecuteCommand( new LoadCargoCommand( sourceStockpile.Stockpile, cartViewModel.Cart, notification.Quantity ) );
    } );

... and from the View:

<i:Interaction.Triggers>
    <interactionRequest:InteractionRequestTrigger 
        SourceObject="{Binding SelectQuantityRequest, Mode=OneWay}">
        <framework:FixedSizePopupWindowAction>
            <interactionRequest:PopupWindowAction.WindowContent>
                <views:SelectSampleDataForImportPopup/>
            </interactionRequest:PopupWindowAction.WindowContent>
        </framework:FixedSizePopupWindowAction>
    </interactionRequest:InteractionRequestTrigger>
</i:Interaction.Triggers>

Additionally, we need a class to hold the data that's passed around, and a ViewModel/View pair for the interaction itself.

Here's the data holding class (note that Maximum is passed to the interaction, and Quantity returned from it):

internal class SelectQuantityNotification : Confirmation
{
    public int Maximum
    {
        get;
        set;
    }

    public int Quantity
    {
        get;
        set;
    }
}

This is the View of the interaction popup:

<UserControl x:Class="ClientModule.Views.SelectSampleDataForImportPopup"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:prism="http://prismlibrary.com/"
         prism:ViewModelLocator.AutoWireViewModel="True"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel Orientation="Vertical">
        <TextBlock>
            Amount: <Run Text="{Binding Quantity}"/>
        </TextBlock>
        <Slider Orientation="Horizontal" Minimum="0" Maximum="{Binding Maximum}" Value="{Binding Quantity}" TickPlacement="BottomRight"/>
        <Button Content="Ok" Command="{Binding OkCommand}"/>
    </StackPanel>
</UserControl>

and it's ViewModel:

internal class SelectSampleDataForImportPopupViewModel : BindableBase, IInteractionRequestAware
{
    public SelectSampleDataForImportPopupViewModel()
    {
        OkCommand = new DelegateCommand( OnOk );
    }

    public DelegateCommand OkCommand
    {
        get;
    }

    public int Quantity
    {
        get { return _notification?.Quantity ?? 0; }
        set
        {
            if (_notification == null)
                return;
            _notification.Quantity = value;
            OnPropertyChanged( () => Quantity );
        }
    }

    public int Maximum => _notification?.Maximum ?? 0;

    #region IInteractionRequestAware
    public INotification Notification
    {
        get { return _notification; }
        set
        {
            SetProperty( ref _notification, value as SelectQuantityNotification );
            OnPropertyChanged( () => Maximum );
            OnPropertyChanged( () => Quantity );
        }
    }

    public Action FinishInteraction
    {
        get;
        set;
    }
    #endregion

    #region private
    private SelectQuantityNotification _notification;

    private void OnOk()
    {
        if (_notification != null)
            _notification.Confirmed = true;
        FinishInteraction();
    }
    #endregion
}
Methistemi
  • 35
  • 6
Haukinger
  • 10,420
  • 2
  • 15
  • 28
  • Thank you. I will try it out. – harpagornis Feb 07 '16 at 01:53
  • One thing I have different is that I was trying to do an InteractionRequest in the TwoWay Mode. Another is that I am using the MetroWindow for the confirmation popup, ie. wrapperWindow.DataContext = notification; That sets my popup's DataContext to be the InteractionRequest, So to modify my code to match the suggestion, I would need to somehow update the Notification property in popup's viewmodel when it takes over as the new DataContext. – harpagornis Feb 07 '16 at 05:40
  • TwoWay or OneWay affects the binding only, I use OneWay, because my `SelectQuantityRequest`-Property is readonly and doesn't need to be changed anyway. The Notification-Property comes from implementing `IInteractionRequestAware`, and it should be set by the framework. – Haukinger Feb 07 '16 at 08:56
  • I like the viewmodel approach provided, but in my scenario, I just need a very simple confirmation popup that just returns the confirmation result. There is no visual tree for the confirmation popup, by which I could get a reference the view/viewmodel that raised the InteractionRequest. So an option may be the Mode=TwoWay binding to self, ie. a property in the viewmodel of the view that raised the InteractionRequest. – harpagornis Feb 08 '16 at 14:37
  • Why would you need a reference? Put the handler where your "" interaction request is triggered. Can you provide more code around this? Also, whether you use view + viewmodel or just code behind in the popup window doesn't really matter, and you can leave the PopupWindowAction alone and use the default, unless you want to actually customize the window. But either way, it is the wrong place for implementing `IInteractionRequestAware`. Set its `PopupWindowAction.WindowContent` in the definition of your trigger instead. – Haukinger Feb 08 '16 at 14:54
  • Put a handler where? I don't have a viewmodel for the popup, and prefer it that way unless it is really necessary. I need a property to which to bind the confirmation result, but why can't that be self, ie. MyDetailViewModel? I edited the question to show how I raise the InteractionRequest in MyDetailViewModel. – harpagornis Feb 08 '16 at 15:37
  • I tried several variations of the xaml shown above for , and that one works to create the confirmation window. I am using a MetroWindow so I get the styling functionality. I could not get this line to work in the xaml: . So I left it out, but the popup is appearing when called by the Raise method. – harpagornis Feb 08 '16 at 15:49
  • To carry information to and from the popup, derive from `Confirmation` and add properties for the data. Then access those from the popup, either from the popup's viewmodel or it's codebehind. Then, in the callback `c =>{if (c.Confirmed) { UoW.AdrTypeRos.Submit();}`, you can access those exact same properties on the exact same data transfer object that was initially passed to and modified by the popup. – Haukinger Feb 08 '16 at 15:51
  • Thank you, I appreciate the help. What would the code look like if I derive from Confirmation in the code behind the popup window so that it updates the public INotification Notification and public Actoin FinishInteraction over in the MyDetailViewModel? Thank you. – harpagornis Feb 08 '16 at 16:18
  • EDIT: That was a typo, I meant to say, the INotification and FinishInteraction in the derived PopupWindowAction1. – harpagornis Feb 08 '16 at 16:28
  • Just use auto properties as a start. If you want to use databinding on properties being forwarded from the data transfer object, you'll need to implement `INotifyPropertyChanged`, of course. In my example, the popup windows's viewmodel does that (with the help of Prism's `BindableBase`). – Haukinger Feb 08 '16 at 17:41