Elmish.wpf: How to bind a TabControl from within another TabControl?

Created on 6 Sep 2020  路  5Comments  路  Source: elmish/Elmish.WPF

In my ignorance, (I'm a newbie), I had hoped that simply expanding upon issue #270 would allow me to put a TabControl within a TabItem of the topmost tab control. I failed miserably right from the start. Assuming all the WPF/XAML/C# are correctly writen, here is the general setup:

XAML Topmost window

<Window x:Class="TabControlDemo2.MainWindow"
             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:local="clr-namespace:TabControlDemo2"
             xmlns:cdv ="clr-namespace:TabControlDemo2.ContactDetail"
             xmlns:vm="clr-namespace:Models3;assembly=TabControlModel"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Window.Resources>
        <!--Whne using DataTemplateSelector,  do not specify any DataType="{x:Type vm:ContactDetail}"-->
            <DataTemplate x:Key="ContactDetailTemplate">
                <cdv:ContactDetailView />
            </DataTemplate>
            <DataTemplate x:Key="InternetTemplate">
                <local:InternetView/>
            </DataTemplate>
            <DataTemplate x:Key="PhoneNumberTemplate">
                <local:PhoneNumberView/>
            </DataTemplate>
            <DataTemplate x:Key="AddressTemplate">
                <local:AddressView/>
            </DataTemplate>
            <local:PropertyDataTemplateSelector x:Key="templateSelector"
                    ContactDetailTemplate="{StaticResource ContactDetailTemplate}"
                    InternetTemplate="{StaticResource InternetTemplate}" 
                    PhoneNumberTemplate="{StaticResource PhoneNumberTemplate}"
                    AddressTemplate="{StaticResource AddressTemplate}"/>
    </Window.Resources>
    <Grid>
        <TabControl ItemsSource="{Binding Details}" ContentTemplateSelector="{StaticResource templateSelector}">
            <TabControl.ItemContainerStyle>
                <Style TargetType="{x:Type TabItem}">
                    <Setter Property="Header" Value="{Binding Name}" />
                </Style>
            </TabControl.ItemContainerStyle>
        </TabControl>
    </Grid>
</Window>

XAML -- Example XAML for ContactDetail

<UserControl x:Class="TabControlDemo2.ContactDetail.ContactDetailView"
             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:local="clr-namespace:TabControlDemo2.ContactDetail"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <!--Whne using DataTemplateSelector,  do not specify any DataType="{x:Type vm:ContactDetail}"-->
        <DataTemplate x:Key="UserControl1Template">
            <local:UserControl1 />
        </DataTemplate>
        <DataTemplate x:Key="UserControl2Template">
            <local:UserControl2/>
        </DataTemplate>
        <DataTemplate x:Key="UserControl3Template">
            <local:UserControl3/>
        </DataTemplate>
        <local:PropertyDataTemplateSelector x:Key="templateSelector"
                    UserControl1Template="{StaticResource UserControl1Template}"
                    UserControl2Template="{StaticResource UserControl2Template}" 
                    UserControl3Template="{StaticResource UserControl3Template}"/>
    </UserControl.Resources>

    <Grid>
        <TabControl ItemsSource="{Binding Details}" ContentTemplateSelector="{StaticResource templateSelector}">
            <TabControl.ItemContainerStyle>
                <Style TargetType="{x:Type TabItem}">
                    <Setter Property="Header" Value="{Binding Name}" />
                </Style>
            </TabControl.ItemContainerStyle>
        </TabControl>
    </Grid>
</UserControl>

My current F# code is:

namespace Models4

open Elmish.WPF
open Elmish

module ContactDetail =
    type DetailType =
        | UserControl1
        | UserControl2
        | UserControl3

    type Detail = { Id: int; Name: string; Type: DetailType }

    type Model =
        { Details: Detail list
          Selected: int option }

    type Msg =
        | Select of int option


    let init () =
        { Details = [{Id=6;  Name="User Control 1"; Type=UserControl1} 
                     {Id=7;  Name="User Control 2"; Type=UserControl2} 
                     {Id=8;  Name="User Control 3"; Type=UserControl3} 
                    ]
          Selected = Some 0 }

    let update msg m =
        match msg with
        | Select entityId -> { m with Selected = entityId }


    let bindings () : Binding<Model, Msg> list = [
        "Details" |> Binding.oneWay (fun m -> m.Details)
    ]


module FrontOffice =

    // This is the TOP MOST set of tabs. Adding definitions here creates more tab items in the top most list.
    type DetailType =
        | ContactDetail
        | Internet
        | PhoneNumber
        | Address

    type Detail = { Id: int; Name: string; Type: DetailType }

    type Model =
        { Details: Detail list
          Selected: int option }

    type Msg =
        | Select of int option

    let init () =
           { Details = [{Id=0;  Name="Contact Detail"; Type=ContactDetail} 
                        {Id=1;  Name="Internet";       Type=Internet} 
                        {Id=2;  Name="Phone Number";   Type=PhoneNumber} 
                        {Id=3;  Name="Address";        Type=Address} 
                        ]
             Selected = Some 3 }

    let update msg m =
        match msg with
        | Select entityId -> { m with Selected = entityId }

    let bindings () : Binding<Model, Msg> list = [
        "Details" |> Binding.oneWay (fun m -> m.Details)
    ]

    let designVm = ViewModel.designInstance (init ()) (bindings ())

    let main window =
        Program.mkSimpleWpf init update bindings
        |> Program.withConsoleTrace
        |> Program.runWindowWithConfig
          { ElmConfig.Default with LogConsole = true; Measure = true }
          window





Clearly, among other things, the binding is wrong. But in trying to move toward subModel, I don't see how to change the DU DetailType in module FrontOffice to accommodate the new module definition of ContactDetail nor how to write the new bindings for the FrontOffice.

Please note that EACH UserControl will also have its own unique properties as they are standard input forms.

Any help is most appreciated. (I'm still learning the process of uploading a visual studio solution to Github).

TIA

Here is the zip file (I hope):

TabControlDemo.zip

Most helpful comment

@bender2k14 Got it! Thank you very much! So putting the DataContext Binding directly in the XAML allows Elmish.WPF to identify it correctly. The clear and simple use of subModel as shown here really should be added to the Elmish.WPF tutorial for simpletons like me. Studying through the submodel tutorial /sample did not help me much. (I am glad, however, that Foggy showed how to work with properties in the code-behind. I did not realize that F# automatically created the "Is Value" for use. I can see this to be a great help down the line). In fact, all the people at F# who helped me have been great!

Learning top-down is the best method for me...now that I am down to first data entry form...I can really start to focus on F#. Thanks again.

All 5 comments

Your ZIP worked perfectly. Great job! :)

And it is a good thing too, because there was a lot in your code that you didn't share above. I have several things to tell you.

First, a disclaimer. I still don't know exactly what you are trying to do. However, by looking at your code (which I finally have!) and reading your above comment, I think I have a good idea. I could have misunderstood something though, so please correct me if I made a false assumption.

GitHub repo

I committed the files in your ZIP into this repository. Look at the commits to see what I changed, and clone the repo to test my changes for yourself.

Project format

You are using the old format for project files. I recommend using the new (SDK-style) format for your project files. You can read more about that here.

After I got your code to compile and removed unused files, I made this change.

Storage of selected tab

Models3.FrontOffice.update was never called. The selected tab was only stored in WPF. I fixed this.

Simple TabControl use

I don't think you need to use TabControl in that complicated way. The impression I get is that you have a constant number of tab types and exactly one of each type of tab. As such, you can just list them all in your XAML code as I did. If I correctly understand what you are trying to do, then you don't need to do anything in the code behind.

Design time view model

I added a design time view model for MainWindow.

Tabs within tabs

Since I drastically simplified your top-level use of tabs, I decided not to implement nested tabs. Try to do this yourself.

I will close this issue, but feel free to make further comments to update us on your progress and ask any questions that you have about TabControls.

@bender2k14 Just received this. Much to study here. Out of curiosity, I would still be interested in seeing how nested tabcontrols would work. In C#, I tend to break things down into "layers" allowing more and more substitutions as the application gets deeper and deeper. I'm not yet experienced with F# to see how this concept would work out. Thanks much...study in progress.

As an aside, would i go the route of subModel for nested tabs, or subModelSeq...??? Thanks

If you have a constant number of submodels, then use the SubModel binding. If you have a variable number of submodels, then use SubModelSeq. Try out the SubModel and SubModelSeq sample projects to gain a better understanding of this.

@bender2k14 Got it! Thank you very much! So putting the DataContext Binding directly in the XAML allows Elmish.WPF to identify it correctly. The clear and simple use of subModel as shown here really should be added to the Elmish.WPF tutorial for simpletons like me. Studying through the submodel tutorial /sample did not help me much. (I am glad, however, that Foggy showed how to work with properties in the code-behind. I did not realize that F# automatically created the "Is Value" for use. I can see this to be a great help down the line). In fact, all the people at F# who helped me have been great!

Learning top-down is the best method for me...now that I am down to first data entry form...I can really start to focus on F#. Thanks again.

FYI: I renamed the repository I shared in https://github.com/elmish/Elmish.WPF/issues/274#issuecomment-688019614 to this repository.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

BentTranberg picture BentTranberg  路  14Comments

TysonMN picture TysonMN  路  13Comments

TysonMN picture TysonMN  路  10Comments

dsyme picture dsyme  路  10Comments

BentTranberg picture BentTranberg  路  4Comments