Advancing Programmatically, MVVM

Wizard for WPF Forum

Posted 7 years ago by Justin Klein
Version: 16.1.0635
Avatar

Hi there,

 

I'd like to advance the Wizard page programmatically from the ViewModel, without breaking MVVM.  My thought was to bind to SelectedIndex, then simply increment it - however, it looks like SelectedIndex is not a DependencyProperty, so it cannot be bound.  The only examples of programmatic navigation I was able to find utilize CodeBehind, and thus, are not MVVM.

 

Is there any workaround, or the only way to move to the next page is to break MVVM?

 

Thanks!

Comments (8)

Posted 7 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Justin,

Yes unfortunately you would need to probably create an attached property that you would bind to instead.  You could have updates to that property set the Wizard.SelectedIndex property if it is a different value.  Then also watch the SelectedPageChanged event and update your attached property in that event handler with the Wizard.SelectedIndex property.  If you do those things, then you can bind to your custom attached property in a MVVM way.


Actipro Software Support

Posted 7 years ago by Justin Klein
Avatar

Got it - thanks :)

Posted 7 years ago by Justin Klein
Avatar

...Actually, it looks like there's still a problem. I've got the attached behavior all wired up, & it works in both directions (i.e. bringing wizard.SelectedIndex's value in via an event subscription, & pushing the attached property's index updates out to the wizard). However, there seems to be an issue in how the wizard fires its events.

Given 2 pages, where Page2 has a "Selected" event. When that event is invoked, e.OriginalSource.SelectedIndex shows Page2's index - as expected. However, the wizard's SelectedPageChanged event has *not* yet fired at that time (even though the selected page has already changed). Thus the attached property still shows SelectedIndex as 1, despite the SelectedIndex actually being 2.

It seems like a page's "Selected" event shouldn't be getting called, showing an already-updated SelectedPage / SelectedIndex value, unless the SelectedPageChanged has indicated that the SelectedPage has...changed, no?

Posted 7 years ago by Justin Klein
Avatar

Basically, what I'm trying to do is this. I've got an "export" wizard with several export types; some of the exports run immediately, & some run in a background worker thread. The wizard's path is like

ExportType->ExportOptions->Progress->Result

The Progress page's Selected event is where it begins the actual export. For exports that use a worker thread, the worker thread does its thing, updating a progressbar, & when done, it increments SelectedIndexBindable to automatically move the wizard forward to "Result" (via the attached behavior discussed above).  This works properly. For exports that don't use a worker thread, the Progress page's Selected event just does the (nearly instant) export & immediately increments SelectedIndex, so to the user it should look like the Progress dialog just pops up for an instant (or maybe not at all), then moves straight to the result.

However, when the Progress page's Selected event is fired, the SelectedPageChanged event has *not* yet fired (despite the SelectedPage having apparently already changed), so SelectedIndexBindable still corresponds to the page *before* the progress page. So incrementing it does nothing, as you're just incrementing it "to the progress page" (which is where it should already be).

Does that make sense?

Posted 7 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Justin,

The Wizard.PreviewSelectedPageChanged event fires right before the old page's Unselected and the new page's Selected events fire.  Then finally Wizard.SelectedPageChanged fires after all those.  If you switch your update to be done in PreviewSelectedPageChanged, then that should fix the problem.


Actipro Software Support

Posted 7 years ago by Justin Klein
Avatar

Perfect, thanks again :)

Posted 6 years ago by Peter Jorgensen
Avatar

Hi Justin,

 

I'm wondering if you'd possibly be willing to share an example of how you're binding a WizardPage with a ViewModel and then link up the wizard page template to viewmodel specific properties? There are no code examples for the Wizard Control utilizing MVVM and while I've been able to data-bind some properties, trying to get a wizardpage bound to a viewmodel has escaped me. The closest I've gotten is extending the wizardpage with a wrapper class that has a viewmodel property, but then I can't get the template to draw. I appreciate anything you're willing to share!

Posted 6 years ago by Justin Klein
Avatar
    public class WizardBehavior : DependencyObject
    {
        /// <summary>
        /// Register the attached property.
        /// Note: WPF will only call the OnChanged callback when the value *actually* changes - thus, I have a default of 
        /// -1, so the first time the binding sets it to "0", the callback will get called.
        /// </summary>
        public static readonly DependencyProperty SelectedIndexBindableProperty =
            DependencyProperty.RegisterAttached("SelectedIndexBindableProperty", 
            typeof(int), typeof(WizardBehavior),
            new FrameworkPropertyMetadata(-1, OnSelectedIndexBindablePropertyChanged)
            {
                BindsTwoWayByDefault = true,
            });

        /// <summary>
        /// Boilerplate
        /// </summary>
        public static void SetSelectedIndexBindableProperty(DependencyObject obj, int value) { obj.SetValue(SelectedIndexBindableProperty, value); }
        public static int GetSelectedIndexBindableProperty(DependencyObject obj)             { return (int)obj.GetValue(SelectedIndexBindableProperty); }

        //Used to prevent cyclical changes
        private static bool _syncingIn;
        private static bool _syncingOut;

        /// <summary>
        /// Sync changes between ActiPro's non-bindable SelectedIndex property & this new SelectedIndexBindable property
        /// </summary>
        private static void OnSelectedIndexBindablePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (_syncingIn) return;
            var wizard = obj as Wizard;
            if (wizard == null) return;

            //Sync Wizard.SelectedIndex -> SelectedIndexBindable
            //Doing -= then += ensures that it's subscribed only once - https://stackoverflow.com/questions/136975/has-an-event-handler-already-been-added
            wizard.SelectedPageChanged -= WizardSelectedPageChanged;
            wizard.SelectedPageChanged += WizardSelectedPageChanged;

            //Sync SelectedIndexBindable -> Wizard.SelectedIndex
            _syncingOut = true;
            wizard.SelectedIndex = (int)(e.NewValue);
            _syncingOut = false;
        }

        /// <summary>
        /// Notified when Actipro's SelectedPage changed; push the changes to this SelectedIndexBindable
        /// </summary>
        private static void WizardSelectedPageChanged(object sender, WizardSelectedPageChangeEventArgs e)
        {
            if (_syncingOut) return;
            _syncingIn = true;
            ((Wizard)sender).SetValue(SelectedIndexBindableProperty, ((Wizard)sender).SelectedIndex);
            _syncingIn = false;
        }
    }
The latest build of this product (v24.1.1) was released 2 months ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.