Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to bind a TabControl from within another TabControl? #274

Closed
awaynemd opened this issue Sep 6, 2020 · 5 comments
Closed

How to bind a TabControl from within another TabControl? #274

awaynemd opened this issue Sep 6, 2020 · 5 comments

Comments

@awaynemd
Copy link

awaynemd commented Sep 6, 2020

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

@TysonMN
Copy link
Member

TysonMN commented Sep 7, 2020

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.

@TysonMN TysonMN closed this as completed Sep 7, 2020
@awaynemd
Copy link
Author

awaynemd commented Sep 7, 2020

@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

@TysonMN
Copy link
Member

TysonMN commented Sep 7, 2020

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.

@awaynemd
Copy link
Author

awaynemd commented Sep 8, 2020

@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.

@TysonMN
Copy link
Member

TysonMN commented Sep 13, 2020

FYI: I renamed the repository I shared in #274 (comment) to this repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants