-1

Using Catel 3.9 and DevExpress 15.x

My Customer has asked that I make some UI changes to an app and I'm not sure it can easily be done.

Architecture:

  • There is a MainWindow with an associated View and ViewModel.
  • The MainWindow holds a TabControl where each Tab's content is a separate View/ViewModel. The MainWindow ViewModel does NOT own any of the nested VMs; they are constructed automatically by Catel at runtime by the View.

The old UI had buttons on each TabItem which allowed the Customer to load, save, display, filter, etc. The commands/properties for these buttons were bound directly to the ViewModel for that tab and was working fine.

The Customer would rather have a single top level (on the MainWindow) menu and selections from that menu would affect whichever tab had the current focus.

I can pass commands (using Messaging or Catel's InterestedIn attribute) to the correct ViewModel, but I'd like to have a more direct binding with the top level menu and the appropriate ViewModel so I can enable/disable menu items or even modify the text to suit whichever tab is open.

I'm looking for a primarily XAML and/or Catel solution. If you need additional information, please let me know.

Suggestions?

thanks, randy

Edit: Sorry that I didn't include additional research. If you knew me, you'd know I will spend hours/days looking for solutions to problems and only when I'm stumped will I ask for assistance. My bad for not including more.

The hardest part about this issue is defining good search parameters. Most of the suggestions were similar to: Just put everything into the MainWindow ViewModel which (to me) is not a good design choice because what is displayed on the tabs IS different and should be separate.

Other solutions were to have the MainWindow ViewModel construct each of the inner ViewModels and then manage them. With the Catel framework I'm using, the framework automatically constructs the VM when the View is loaded, injecting any required parameters to the constructor. See below -- you just reference the View and Catel will match it up with its ViewModel and create it for you. Unfortunately without taking other steps, you really don't have a reference to the VM that was created.

MainWindow.xaml:

<dx:DXTabControl x:Name="MainTabControl" 
                         Grid.Row="1"
                         Margin="10" 
                         BorderThickness="0" 
                         SelectedIndex="{Binding SelectedTabIndex}"
                         >
            <dx:DXTabItem Header="Getting Started" IsEnabled="True">
                <views:GetStarted />
            </dx:DXTabItem>
            <dx:DXTabItem Header="Validate Student Records" Background="Ivory">
                <views:StudentValidation />
            </dx:DXTabItem>
            <dx:DXTabItem Header="Validate Teacher Records" Background="Ivory">
                <views:TeacherValidation />
            </dx:DXTabItem>
            <dx:DXTabItem Header="Validate Admin Records" Background="Ivory">
                <views:AdminRecordValidation />
            </dx:DXTabItem>
        </dx:DXTabControl>

Examples of some possible solutions I'm looking at:

Edit #2: There was a suggestion about using a Service and SO doesn't allow you to add detailed comments (and it puts a TIMER on your comments -- SHEEESH!), so I'll put my response here.

Consider this scenario using a Service: The Customer starts the app and clicks on a Tab (StudentValidation). The MainWindowViewModel (via a Property) detects the selected tab and calls the Service with an update (I'm not sure what is updated; possibly some sort of State). The "nested" ViewModels are notified (via an Event) of the change in the Service. I'll assume the StudentValidationViewModel is the only one who actually responds to the event and interacts with the Service, retrieving "data".

So, now we have the StudentValidation tab displayed and the Customer goes to the Main Menu of the app. The Main Menu is STILL tied to the MainWindow and every command is bound to the MainWindowViewModel. How does the Service bind the Main Menu to the ViewModel of the currently selected tab so that the commands will be handled by the StudentValidationViewModel? I'm probably missing something.

Community
  • 1
  • 1
RandyB
  • 325
  • 4
  • 13

3 Answers3

0

Use a singleton model which holds the shared data so you get the instance from wherever you like.

Marin Althuis
  • 168
  • 3
  • 14
  • I think I know where you are going with this. Interesting -- let me see if something along these lines will work and still stay true to MVVM. (I.e., we really don't want the ViewModel having direct access to the Menu UI elements to modify.) Thanks! – RandyB Jul 31 '15 at 19:34
  • Oh, I'm using this method in a project atm. It really depends on the architecture of your application, but it serves me quite well. – Marin Althuis Aug 03 '15 at 06:10
0

Services are the solution. Create a solution that is injected into all view models. Then the top-level vm can update the service, and all vm's can respond to the update via events.

Remember that vm's just represent a live view in memory so you can interact with them.

Geert van Horrik
  • 5,689
  • 1
  • 18
  • 32
  • I use Services all the time in my app and they are very useful. But while they solve a lot of problems, I'm not sure they solve the issue of trying to bind something in the MainWindow view with the ViewModel of the selected Tab. See Edit #2 above. Thanks! – RandyB Aug 03 '15 at 18:20
0

Thank you for all the suggestions. I tried to upvote each one, but as a "newbie" I can't. As I worked thru each one, I realized that all I wanted to do was bind a specific subset of the Main Menu items to the View/ViewModel of the currently focused tab on the Main Window. It seemed as simple as changing the DataContext of the menu item.

Here's the Main Menu. The FileSubMenu is the one I need to bind to the currently focused ViewModel. The other menu items can be handled by the MainWindowViewModel.

MainWindow.xaml:

<dxb:MainMenuControl Grid.Row="0" 
                     HorizontalAlignment="Left"
                     HorizontalContentAlignment="Stretch"
                     VerticalAlignment="Top"
                     BarItemDisplayMode="ContentAndGlyph"
                     >
    <dxb:BarStaticItem Content="Validator" Glyph="pack://application:,,,/LexValidator;component/Images/lex-logo.png" />
    <dxb:BarSubItem x:Name="FileSubMenu" Content="File">
        <dxb:BarButtonItem Content="{Binding LoadRecordsText}" Glyph="{dx:DXImage Image=LoadFrom_16x16.png}" Command="{Binding LoadRecordsFile}"/>
        <dxb:BarButtonItem Content="Clear Display" Glyph="{dx:DXImageOffice2013 Image=Clear_16x16.png}" Command="{Binding ClearDisplay}"/>
        <dxb:BarButtonItem Content="FTE Counts..." Glyph="{dx:DXImage Image=TextBox_16x16.png}" Command="{Binding ShowFTECounts}"/>
        <dxb:BarButtonItem Content="Show Pay Grid..." Glyph="{dx:DXImage Image=Financial_16x16.png}" Command="{Binding ShowPayGrid}"/>
        <dxb:BarItemLinkSeparator />
        <dxb:BarCheckItem Content="Show Ignored Issues" Glyph="{dx:DXImage Image=ClearFilter_16x16.png}" IsChecked="{Binding ShowIgnoredIssues}" IsEnabled="{Binding IsShowIgnoredIssuesEnabled}" />
    </dxb:BarSubItem>
    <dxb:BarSubItem Content="Exit">
        <dxb:BarButtonItem Content="Exit" Glyph="{dx:DXImage Image=Close_16x16.png}" Command="{Binding ExitApplication}"/>
    </dxb:BarSubItem>
    <dxb:BarSubItem Content="Help">
        <dxb:BarButtonItem Content="About..." Glyph="{dx:DXImageGrayscale Image=Index_16x16.png}" Command="{Binding ShowAboutBox}"/>
    </dxb:BarSubItem>
</dxb:MainMenuControl>

Then on the TabControl, I handle the event when a new tab is selected:

MainWindow.xaml.cs

private void MainTabControl_SelectionChanged(object sender, TabControlSelectionChangedEventArgs e)
{
    // Turn it off and see if it needs to be enabled.
    this.FileSubMenu.IsEnabled = false;

    var newTabItem = e.NewSelectedItem as DXTabItem;
    if (newTabItem != null)
    {
        var tabView = newTabItem.Content as UserControl;
        if (tabView != null)
        {
            var tabViewModel = tabView.ViewModel;
            if (tabViewModel != null)
            {
                this.FileSubMenu.DataContext = tabViewModel;
                this.FileSubMenu.IsEnabled = true;
            }
        }
    }
}

I realize this may not be very "MVVM", but it works well and the "Boss" said "Move on to something else". I would be happier if the above code could be handled totally in XAML -- some sort of resource maybe?

Again, if there is something I missed or a better (more MVVM) solution, please let me know.

RandyB
  • 325
  • 4
  • 13