Binding different views to viewmodel

Dec 22, 2012 at 3:00 PM

I've created my entire first application using Okra! I love it. 

Now some of my elder-nearly-blind users are complaining the default styles are too small and they can't read them.  So I thought, no problem! MVVM should make it easy to just make a new view of the same information (vm) but re-designed for the nearly blind!

Using the PageExport and ViewModelExport properties, Okra auto-magically wires everything up for you.  So, now that I have duplicate views for each page, how do I wire one or the other up based on a setting? 

Or maybe that's the wrong thought process... should I instead use another view-state? 

Thanks again for the help and this great framework!

Coordinator
Dec 22, 2012 at 5:54 PM

Hi,

Good to hear you are getting on well with the Okra App Framework.

Regarding your query, have you checked out the build in support for high-contrast themes - http://msdn.microsoft.com/en-us/library/windows/apps/xaml/Hh868165%28v=win.10%29.aspx. As far as I'm aware this just tweaks the colours to be easier to read rather than the size of anything however.

If you decide that you wish to change the view based upon a configuration setting then the best place for this would be a custom IViewFactory implementation (the existing on is in Okra.MEF.Navigation). This is the code that takes a page name and uses MEF to resolve and compose the page & view-model combination to display. This code should be able to be modified to be able to choose a different view based on configuration. Note that the default OkraBootstrapper will include the default ViewFactory implementation so you will need to override GetContainerConfiguration() and copy and paste the code from GetOkraContainerConfiguration() - this is so you can change the last line of .WithAssembly(...) to select just the types to export rather than everything from Okra.MEF. A bit of work but should be fine.

In this case however I'd be tempted to try to use the same view with an additional view-state (the same way different orientations are implemented). You can edit this fairly easily in Blend and use some code-behind to choose the right view-state based upon the configuration (or even the accessibility flags mentioned in the link above?).

Keep me updated on how you get on.

Regards,

    Andy

Coordinator
Dec 23, 2012 at 5:46 PM

Another option I thought of if you want to use different views (although I'm personally still favouring the visual state manager),

You could adopt a naming convention for your pages with e.g. views named "Home" and "Home_Accessible". You could then write a single view-model for both names (since you can have multiple ViewModelExportAttributes applied to it). At any place you perform a navigation you would simply check which view to display and pass the relevant page name. You could even use an extension method on INavigationBase to perform this logic.

Problems with this approach would be that you would have to restart the application for any changes to be applied (since the wrong page names would be in memory). Actually this would also apply to the custom IViewFactory approach, since the previously created views would still be in memory. In this respect the visual state manager approach would be great as it would allow you to change the look of the UI on the fly.

Andy

Feb 5, 2013 at 3:31 PM
Hey Andy,
I'm finally getting around to adding this in. I've thought the best way would be to add in view states like we talked about. I'm thinking the best way to do this, while still taking into effect the ApplicationViewState (orientation/snapped) would be to import my DefaultSettings class into the LayoutAwarePage.cs provided in the Common folder. I could then check both my ApplicationView.Value and my DefaultSettings.UseAltView property and set any layoutAwareControls to the required visual state. I would do that by modifying the code below in the LayoutAwarePage.cs:
        public void InvalidateVisualState()
        {
            if (this._layoutAwareControls != null)
            {
                string visualState = DetermineVisualState(ApplicationView.Value, DefaultSettings.UseAltView);
                foreach (var layoutAwareControl in this._layoutAwareControls)
                {
                    VisualStateManager.GoToState(layoutAwareControl, visualState, false);
                }
            }
        }

        protected virtual string DetermineVisualState(ApplicationViewState viewState, bool useAlt)
        {
            if(useAlt)
            {
                    return "Alt_" + viewState.ToString();
            }

            return viewState.ToString();
        }
...I would obviously then have copies of every ApplicationViewState on my view named "Alt_NameOfAppViewState". Assuming there is nothing terribly special about the ApplicationViewStates State group on each Page, I'm just going to create another State group called Alt_ApplicationViewStates with my "Alt_" named states. Going to try this sometime this week, but wanted to get your opinion on the proposed solution. My only beef with it is this: What if I want to add another 'view' sometime in the future? I have 2 'views' now, so this all seems fine, but what if I wanted to add themes or something later on? I'd have to make my UseAltView property setting an enum instead of a bit field and do a case statement in my LayoutAwarePage.cs instead of an 'if' statement. Also, I would then have to create a new state group with 4 states for each and every theme/view type? Sounds like a lot of overhead.
For now this will work, as I only require 2 views, but I'd love to hear your ideas thinking ahead for say, 3-5 views.
Coordinator
Feb 6, 2013 at 12:47 PM
Hi again,

I think you should be able to simplify the visual states here. It is important to remember that the visual state groups are independent and a page can be in multiple visual states at any one time (but only one per visual state group). In other words you do not need,
  • ApplicationViewStates : FullScreenLandscape, Filled, FullScreenPortrait, Snapped
  • Alt_ApplicationViewStates : Alt_FullScreenLandscape, Alt_Filled, Alt_FullScreenPortrait, Alt_Snapped
And instead you should use,
  • ApplicationViewStates : FullScreenLandscape, Filled, FullScreenPortrait, Snapped
  • AltViewStates : Alt_Normal, Alt_Accessible (... and any other themes in the future)
You can then leave the LayoutAwarePage code alone (I'd advise not touching this too much!) and create your own ThemeablePage that derives from LayoutAwarePage. For example, if you had a simple bool property on the page (in reality you would need to hook into changes on the DefaultSettings class),
public class ThemeablePage : LayoutAwarePage
{
    private bool isAccessibleView;

    public bool IsAcessibleView
    {
        get { return isAccessibleView; }
        set
        {
            isAccessibleView = value;
            string viewStateName = isAccessibleView ? "Alt_Accessible" : "Alt_Normal";
            VisualStateManager.GoToState(this, visualStateName, false);
        }
    }
}
When you define the visual states (I'd advise using Blend for this) you can then define your ApplicationViewStates as normal to account for orientation changes, and in the AltViewStates you could say Alt_Accessible has text in a larger font size with a high contrast background. The visual state manager will then automatically combine these to give a SnappedView + large text/high contrast background.

Does this all make sense?
Andy
Feb 6, 2013 at 3:22 PM
That all makes a lot of sense. And done! I made a new state group for Normal and Accessible as you mentioned. Then I made a simple public class that has constant strings and a List of those strings that are my different view states in my new visual state group. I exposed this in my Settings charm view/viewmodel and save the selected item in the settings. Then since I'm using MVVM Light as well, I just shoot off a message when the value changes and catch it on my LayoutAwarePage_Okra.cs. This class is just a modified version I made that takes out all the stuff I don't need and adds in some Okra/personal stuff. Since the message is the new view state value, I just update all my _layoutAwareControls, much like the InvalidateVisualState method from the default LayoutAwarePage.cs:
// Added for View Types & Themes (normal, accessible, etc)
        private void ChangeViewThemeState(string newSettingValue)
        {
            if (this._layoutAwareControls != null)
            {
                foreach (var layoutAwareControl in this._layoutAwareControls)
                {
                    VisualStateManager.GoToState(layoutAwareControl, newSettingValue, false);
                }
            }
        }
Thanks for the help! This way is much easier than what I was talking about, and leaves it open for more themes later on!
Coordinator
Feb 7, 2013 at 12:54 PM
Edited Feb 7, 2013 at 12:54 PM
That's great!

Always pleased to be able to help,
Andy