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

WPF DataTemplate X:Type receiving "Elmish.WPF.ViewModel'2[System.Object.System.Object]" #270

Closed
awaynemd opened this issue Aug 23, 2020 · 28 comments

Comments

@awaynemd
Copy link

(I'm a newbie here and haven't got the hang of making a branch as yet :))

I am trying to make this WPF XAML work. The problem is Elmish.WPF is returning

Elmish.WPF.ViewModel'2[System.Object.System.Object]

and not one of the expected types of ContactDetail, Internet, etc..so the DataTemplate fails to branch.

How do I fix this?

Thank you for any help.

<TabControl Grid.Row="1" ItemsSource="{Binding Details}">
          <TabControl.ItemContainerStyle>
              <Style TargetType="{x:Type TabItem}">
                  <Setter Property="Header" Value="{Binding Name}" />
              </Style>
          </TabControl.ItemContainerStyle>
          <TabControl.Resources>
              <DataTemplate DataType="{x:Type vm:ContactDetail}">
                  <vi:ContactDetailsView />
              </DataTemplate>
              <DataTemplate DataType="{x:Type vm:Internet}">
                  <vi:InternetView/>
              </DataTemplate>
              <DataTemplate DataType="{x:Type vm:PhoneNumber}">
                  <vi:PhoneNumbersView/>
              </DataTemplate>
              <DataTemplate DataType="{x:Type vm:Address}">
                  <vi:AddressesView/>
              </DataTemplate>
          </TabControl.Resources>
      </TabControl>

Here is the supporting F# file using Elmish.WPF

`namespace Models

open Elmish.WPF
open Elmish
open System
open System.Windows

type ContactDetail = { Name: string; Content: string; Text: string }
type Internet      = { Name: string; Content: string; Text: string }
type PhoneNumber   = { Name: string; Content: string; Text: string }
type Address       = { Name: string; Content: string; Text: string }
module FrontOffice =    
    type Details =
        | ContactDetail of ContactDetail * Id: Guid
        | Internet      of Internet   * Id: Guid
        | PhoneNumber   of PhoneNumber   * Id: Guid
        | Address       of Address  * Id: Guid
        
        member this.id = 
               match this with
               | ContactDetail(_, id)
               | Internet(_, id)
               | PhoneNumber(_, id)
               | Address(_, id) -> id

        member this.name = 
            match this with
            | ContactDetail(cd,_) -> cd.Name
            | Internet(i,_)  -> i.Name
            | PhoneNumber(pn,_) -> pn.Name
            | Address(ad,_) -> ad.Name 

        member this.content = 
            match this with
            | ContactDetail(cd,_) -> cd.Content
            | Internet(i,_)  -> i.Content
            | PhoneNumber(pn,_) -> pn.Content
            | Address(ad,_) -> ad.Content 

    let contactDetail  : ContactDetail = { Name="Contact Detail"; Content="Content for Contact Detail"; Text="here is the contact detail text" }    
    let internet       : Internet = { Name="Internet";       Content="Content for Internet";       Text="here is the internet text" }
    let phoneNumber    : PhoneNumber =  {Name="Phone Number";   Content="Content for phone number";   Text="here is the phone number text" }
    let address        : Address = { Name="Address";        Content="Content for Address";        Text="here is the Address text" }
   
    let details   = [ContactDetail (contactDetail,Guid.NewGuid())
                     Internet      (internet,Guid.NewGuid())
                     PhoneNumber   (phoneNumber,Guid.NewGuid())
                     Address       (address,Guid.NewGuid())
                     ]

    // Each instance will hold one of ContactDetails, Internet, Phonenumber, and
    // each has a common property of ID (the Guid). This way, you simplify your model
    type DetailsWithId = DetailsWithId of Details * Guid

    /// This is the main data model for our application
    type Model = {
      ClickCount: int
      Message: string
      Details: Details list
    }

    /// This is used to define the initial state of our application. It can take any arguments, but we'll just use unit. We'll need the Cmd type.
    /// Notice that we return a tuple. The first field of the tuple tells the program the initial state. The second field holds the command to issue.
    /// This is the standard Elmish init() (not special to Elmish.WPF).
    let init() = 
       {
          ClickCount = 0
          Message = "Hello Elmish.WPF"
          Details = details
       }

    /// This is a discriminated union of the available messages from the user interface
    type Msg =
      | ButtonClicked
      | Reset

    /// This is the Reducer Elmish.WPF calls to generate a new model based on a message and an old model.
    /// The update function will receive the change required by Msg, and the current state. It will produce a new state and potentially new command(s).
    let update (msg: Msg) (model: Model) =
      match msg with 
      | ButtonClicked -> {model with ClickCount = model.ClickCount + 1}
      | Reset -> init()

 
    /// Elmish.WPF uses this to provide the data context for your view based on a model.
    /// The bindings is the view for Elmish.WPF
    /// Define the “view” function using the Bindings module. This is the central public API of Elmish.WPF. Normally in Elm/Elmish this 
    /// function is called view and would take a model and a dispatch function (to dispatch new messages to the update loop) and return 
    /// the UI (e.g. a HTML DOM to be rendered), but in Elmish.WPF this function is in general only run once and simply sets up bindings 
    /// that XAML-defined views can use. Therefore, it is called bindings instead of view.
    let bindings(): Binding<Model, Msg> list =
        [
          // One-Way Bindings
          "ClickCount" |> Binding.oneWay (fun m -> m.ClickCount)
          "Message" |> Binding.oneWay (fun m -> m.Message)
          "Details" |> Binding.subModelSeq((fun m -> m.Details), (fun detail -> detail.id), fun () ->
              [
                  "Id"   |> Binding.oneWay (fun (_, detail) -> detail.id)
                  "Name" |> Binding.oneWay (fun (_, detail) -> detail.name)
                  "Content" |> Binding.oneWay (fun (_,detail) -> detail.content)
              ])

          
          // Commands
          "ClickCommand" |> Binding.cmd ButtonClicked
          "ResetCommand" |> Binding.cmd Reset
        ]

    /// This is the application's entry point. It hands things off to Elmish.WPF     
    let entryPoint (mainWindow: Window) =    
        Program.mkSimpleWpf init update bindings
        |> Program.runWindowWithConfig
                   { ElmConfig.Default with LogTrace = true; Measure = true; MeasureLimitMs = 1 }
                   mainWindow
`
@cmeeren
Copy link
Member

cmeeren commented Aug 24, 2020

Elmish.WPF returns its internal ViewModel because that's exactly what you are specifying here:

"Details" |> Binding.subModelSeq((fun m -> m.Details), (fun detail -> detail.id), fun () ->
    [
        "Id"   |> Binding.oneWay (fun (_, detail) -> detail.id)
        "Name" |> Binding.oneWay (fun (_, detail) -> detail.name)
        "Content" |> Binding.oneWay (fun (_,detail) -> detail.content)
    ])

When you use any subModel binding, Elmish.WPF uses its internal view model and creates properties according to the inner bindings (Id, Name, and Content, in your case).

If you have only one-way bindings and want to return a sequence of custom types, simply do:

"Details" |> Binding.oneWay(fun m -> m.Details)

Or even better, use oneWaySeq if you have many items so that sequence updates are quicker.

Note that you can't have anything other than one-way bindings in this way.

@TysonMN
Copy link
Member

TysonMN commented Aug 24, 2020

Please try to follow the steps in #268 (comment) so you can share a branch with this code that has the behavior you describe.

@awaynemd
Copy link
Author

@cmeeren When doing as you advised, I now am losing the "Name" resolution needed for the tab:

BindingExpression path error: 'Name' property not found on 'object' ''ContactDetail'

My TabItem Header is now blank???

@TysonMN
Copy link
Member

TysonMN commented Aug 26, 2020

We will be better able assist you if you are able to share a branch with this code that has the behavior you describe.

@awaynemd
Copy link
Author

awaynemd commented Aug 26, 2020

@cmeeren I'm guessing what I need is something that functions like this:

   "Details" |> Binding.oneWay(fun m -> m.Details)
                  |> Binding.subModelSeq((fun m -> m.Details), (fun detail -> detail.id), fun () ->
                        [
                            "Id"   |> Binding.oneWay (fun (_, detail) -> detail.id)
                              "Name" |> Binding.oneWay (fun (_, detail) -> detail.name)
                             "Content" |> Binding.oneWay (fun (_,detail) -> detail.content)
                  ])

Suggestions?

Thanks

@awaynemd
Copy link
Author

@bender2k14

It's hard for this old dog to learn new tricks.

Here is what I did:

  1. Clicked on the Fork button, this produced: awaynemd/Elmish.WPF
  2. Then clicked on the code button choosing Download ZIP

Now what?

How do I make a branch and share it? Thanks.

I really would like to get this tab control working... Thanks.

@TysonMN
Copy link
Member

TysonMN commented Aug 26, 2020

It's hard for this old dog to learn new tricks.

That's ok. Elmish.WPF is also a new trick for you, and we will help you learn both Elmish.WPF and GitHub. I think the correct order is to first learn GitHub. Then it will be easier to learn Elmish.WPF.

Do you program professionally? Have you used git?

@TysonMN
Copy link
Member

TysonMN commented Aug 27, 2020

Following these instructions to

  1. create a branch,
  2. add the code you are working on somewhere in the solution (You can add a new project or modify one of the existing samples. Whatever you think is easier.),
  3. commit those changes, and
  4. push your changes to your GitHub fork of Elmish.WPF.

@awaynemd
Copy link
Author

@bender2k14 I made a first attempt at a pull request on this issue. Not sure If I did all that was needed. Please advise. Thanks.

@TysonMN
Copy link
Member

TysonMN commented Aug 29, 2020

The PR in question is #273. I assumed that PR was about issue #268, not this issue. You created both issue #268 and this one (issue #270). Do you have two separate problems that you want help with or just one?

@awaynemd
Copy link
Author

awaynemd commented Aug 30, 2020

@bender2k14 Actually, their both related issues. I am trying to make this simple wpf xaml to work correctly:

<TabControl Grid.Row="1" ItemsSource="{Binding Details}">
            <TabControl.ItemContainerStyle>
                <Style TargetType="{x:Type TabItem}">
                    <Setter Property="Header" Value="{Binding Name}" />
                </Style>
            </TabControl.ItemContainerStyle>
            <TabControl.Resources>
                <DataTemplate DataType="{x:Type vm:ContactDetail}">
                    <vi:ContactDetailsView />
                </DataTemplate>
                <DataTemplate DataType="{x:Type vm:Internet}">
                    <vi:InternetView/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type vm:PhoneNumber}">
                    <vi:PhoneNumbersView/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type vm:Address}">
                    <vi:AddressesView/>
                </DataTemplate>
            </TabControl.Resources>
        </TabControl>

The TabItem Name becomes correctly resolved when the Bindings use subModelSeq:

"Details" |> Binding.subModelSeq((fun m -> m.Details), (fun detail -> detail.id), fun () ->
[
"Id" |> Binding.oneWay (fun (, detail) -> detail.id)
"Name" |> Binding.oneWay (fun (
, detail) -> detail.name)
"Content" |> Binding.oneWay (fun (_,detail) -> detail.content)
])

However, doing so generates a dynamic viewmodel that feeds WPF an xType of:
Elmish.Wpf.ViewModel'2[System.Object.System.Object]

On the other hand, the x:Type correctly resolves when
"Details" |> Binding.oneWay(fun m -> m.Details)
but then the "Name" is no longer resolved.

Hence, #273 and #268 are related (to me). Bottom line, I'm trying to get the above simple wpf xaml to work.

Thanks.

@TysonMN
Copy link
Member

TysonMN commented Aug 30, 2020

Can you change the code in your branch so that it can be immediately executed?

@awaynemd
Copy link
Author

@bender2k14 For old dogs like me, GitHub poses more of a challenge then you might think, but I it appears that I was successful with the pull request.

How do you make an "Immediately Executed" code? (I will try). In the meantime, I hope my last comment explained my problem. Thanks.

@TysonMN
Copy link
Member

TysonMN commented Aug 30, 2020

...it appears that I was successful with the pull request.

Creating a pull request was unnecessary, which is why I closed it. All you needed to do was share a link to your branch, which the PR did accomplish.

Instead of using GitHub, can you share a link to a ZIP of your code?

How do you make an "Immediately Executed" code?

Whether via a ZIP file or GitHub, I would like you to organize your code so that I can open a solution file, set some project as the startup project, and run your code from within Visual Studio. In contrast, I don't want to copy paste your code from multiple code blocks in comments on GitHub and figure out myself how they should be related to each other.

I hope my last comment explained my problem.

I generally understand your problem, but I am concerned that you would be able to understand answers given to at the same level of disorganized detail that characterize your questions. I think the best way to help you is to first help you ask better questions, especially by being able to effectively share the code that you are having trouble with. After we have a more specific question, we will be able to give you a precise answer in return.

@awaynemd
Copy link
Author

awaynemd commented Sep 1, 2020

@bender2k14 Try Try Again. I appreciate your patience. I've attempted to do what you ask, although honestly, I do not know how you can use the result.

Try: https://github.com/awaynemd/TabControlDemo3.git.

This "should" consist of two projects:
TabControlDemo3 in C# (don't ask what happened to demo1 and demo2), and
Models in F#.

TabControlDemo3 is supposed to be the startup project (its not obvious to me that this came through the solution transfer).

If this project is run, what you should see is a nice TabConrol with Headers on each of the tab items. However, clicking on any of the tabitems will show that WPF is unable to find the Object returned by Elmish.WPF.

How'd I do?

@TysonMN
Copy link
Member

TysonMN commented Sep 1, 2020

Getting closer!

There is no solution (sln) file. Also, each project is normally in its own folder, but you have no folders.

I sent you an email to provide more assistance.

@awaynemd
Copy link
Author

awaynemd commented Sep 4, 2020

@bender2k14 I've been playing around with this...but nothing I can do seems to let me upload two project files to the same GitHub repository...What's the trick? (When you say "its own folder", do you mean physical OS file folder, or do you mean separate VS Solution folders?).. Thanks.

@TysonMN
Copy link
Member

TysonMN commented Sep 4, 2020

git and GitHub certainly support folders. The root of this repo contains the folders

  • .github,
  • logo, and
  • src.

Look at this repository that I recently created in GitHub. It mostly contains the files and folder that Visual Studio creates when creating a WPF project and a solution to contain it. Specifically, it has a folder named WpfApp2 that contains all the files related to the project and a solution (sln) file named Temp.sln.

@awaynemd
Copy link
Author

awaynemd commented Sep 4, 2020

@bender2k14 Thanks....but How did you create and upload the folders? Pushing from Visual Studio only seems to allow me the one directory holding the solution and no subdirectories...so the references to projects outside of the top directory fail :(

That is, I need multiple projects in same repo...I know it is supported, but I have no idea how to do it. Thanks.

@TysonMN
Copy link
Member

TysonMN commented Sep 4, 2020

I know other people that interact with git through Visual Studio, but I almost never do. Instead, try interacting with git via the command line or a GUI like Sourcetree.

@FoggyFinder
Copy link

@awaynemd your Model.Details doesn't match types that you specify in your DataTemplates. It won't work that way.

ContactDetail, Internet, PhoneNumber, Address looks like the same type. I think it would be easier to change types to avoid repetitions:

[<RequireQualifiedAccess>]
type DetailType =
    | ContactDetail
    | Internet
    | PhoneNumber
    | Address

type Detail = { Id: Guid; Name: string; Content: string; Text: string ; Type: DetailType }

and to have View that depends on DetailType you can use DataTemplateSelector

@FoggyFinder
Copy link

@awaynemd If you want to keep your model types as is then you can use IValueConverter.

This converter should return the list of real VM types, i.e.:

type GetDetailsVMToResource() =
   let toVM = 
       function
       | Details.Address(a,_) -> a |> box
       | Details.Internet(i, _) -> i |> box
       | Details.PhoneNumber(p, _) -> p |> box
       | Details.ContactDetail(c, _) -> c |> box
   interface IValueConverter with
       member this.Convert(value: obj, targetType: System.Type, parameter: obj, culture: System.Globalization.CultureInfo): obj = 
           match value with
           | :? list<Details> as details ->
               details
               |> List.map toVM
               |> box
           | _ -> failwith "shouldn't happen"
           
       member this.ConvertBack(value: obj, targetType: System.Type, parameter: obj, culture: System.Globalization.CultureInfo): obj = 
           raise (System.NotImplementedException())    

With binding from VM side:

"Details" |> Binding.oneWay(fun m -> m.Details)

and in the xaml

somewhere where you keep resources

        <conv:GetDetailsVMToResource x:Key="toDVMc" />

binding:

<TabControl Grid.Row="1" ItemsSource="{Binding Details, Converter={StaticResource toDVMc}}">

@awaynemd
Copy link
Author

awaynemd commented Sep 5, 2020

@FoggyFinder I really like the idea of the DataTemplateSelector...but am having a hard time making it work. The Tab Item Headers are correctly typed, but I have no idea how to write the DataTemplateSelector (in C#??)

public class PropertyDataTemplateSelector : DataTemplateSelector
    {
        public DataTemplate ContactDetailTemplate { get; set; }
        public DataTemplate AddressTemplate { get; set; }
        public DataTemplate PhoneNumberTemplate { get; set; }
        public DataTemplate InternetTemplate { get; set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
   ????????????
       }        
    }

The 'item' here is something along the lines of 'ViewModel'2<object,object>' and I can tell no difference between which tab item is selected ????? :(o

Here is what I have so far:

namespace Models3

open Elmish.WPF
open Elmish


type ContactDetail = { Name: string; Content: string; Text: string }
type Internet      = { Name: string; Content: string; Text: string }
type PhoneNumber   = { Name: string; Content: string; Text: string }
type Address       = { Name: string; Content: string; Text: string }


module FrontOffice =

    type DetailType =
        | ContactDetail
        | Internet
        | PhoneNumber
        | Address

    type Detail = { Id: int; Name: string; Content: string; Text: 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"; Content="Content for Contact Detail"; Text= "Text for Contact Detail"; Type=DetailType.ContactDetail} 
                        {Id=1;  Name="Internet";       Content="Content for Contact Detail"; Text= "Text for Internet";       Type=DetailType.Internet} 
                        {Id=2;  Name="Phone Number";   Content="Content for Contact Detail"; Text= "Text for Phone Number";   Type=DetailType.PhoneNumber} 
                        {Id=3;  Name="Address";        Content="Content for Contact Detail"; Text= "Text for Address";        Type=DetailType.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.subModelSeq( (fun m -> m.Details), (fun e -> e.Id),
            (fun () -> [
                "Name" |> Binding.oneWay (fun (_, e) -> e.Name)
                "Content" |> Binding.oneWay (fun (_, e) -> e.Content)
            ]))

        "SelectedEntity" |> Binding.subModelSelectedItem("Entities", (fun m -> m.Selected), Select)
    ]

    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

And for the C# XAML:


<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:vm="clr-namespace:Models3;assembly=TabControlModel"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Window.Resources>
        <DataTemplate x:Key="ContactDetailTemplate" DataType="{x:Type vm:ContactDetail}">
                <local:ContactDetailView />
            </DataTemplate>
            <DataTemplate x:Key="InternetTemplate" DataType="{x:Type vm:Internet}">
                <local:InternetView/>
            </DataTemplate>
            <DataTemplate x:Key="PhoneNumberTemplate" DataType="{x:Type vm:PhoneNumber}">
                <local:PhoneNumberView/>
            </DataTemplate>
            <DataTemplate x:Key="AddressTemplate" DataType="{x:Type vm:Address}">
                <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>

Thank you for your help!

@FoggyFinder
Copy link

@awaynemd almost there!

F# part:

  1. Remove:
type ContactDetail = { Name: string; Content: string; Text: string }
type Internet      = { Name: string; Content: string; Text: string }
type PhoneNumber   = { Name: string; Content: string; Text: string }
type Address       = { Name: string; Content: string; Text: string }
  1. Replace
"Details" |> Binding.subModelSeq( (fun m -> m.Details), (fun e -> e.Id),
            (fun () -> [
                "Name" |> Binding.oneWay (fun (_, e) -> e.Name)
                "Content" |> Binding.oneWay (fun (_, e) -> e.Content)
            ]))

with

"Details" |> Binding.oneWay (fun m -> m.Details)
  1. There is typos
let init () =
           { Details = [{Id=0;  Name="Contact Detail"; Content="Content for Contact Detail"; Text= "Text for Contact Detail"; Type=DetailType.ContactDetail} 
                        {Id=1;  Name="Internet";       Content="Content for Contact Detail"; Text= "Text for Internet";       Type=DetailType.Internet} 
                        {Id=2;  Name="Phone Number";   Content="Content for Contact Detail"; Text= "Text for Phone Number";   Type=DetailType.PhoneNumber} 
                        {Id=3;  Name="Address";        Content="Content for Contact Detail"; Text= "Text for Address";        Type=DetailType.Address} 
                        ]
             Selected = Some 3 }

Content is the same for all items. Probably

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

C# part:

  1. Since you're using DataTemplateSelector you don't have to specify DataType

XAML:

<DataTemplate x:Key="ContactDetailTemplate">
    <local:ContactDetailView />
</DataTemplate>
<DataTemplate x:Key="InternetTemplate">
    <local:InternetView />
</DataTemplate>
<DataTemplate x:Key="PhoneNumberTemplate">
    <local:PhoneNumberView />
</DataTemplate>
<DataTemplate x:Key="AddressTemplate">
    <local:AddressView />
</DataTemplate>

Selector:

public class PropertyDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate ContactDetailTemplate { get; set; }
    public DataTemplate AddressTemplate { get; set; }
    public DataTemplate PhoneNumberTemplate { get; set; }
    public DataTemplate InternetTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item is Models.FrontOffice.Detail detail)
        {
            var dType = detail.Type;
            if (dType.IsAddress)
            {
                return AddressTemplate;
            }
            if (dType.IsContactDetail)
            {
                return ContactDetailTemplate;
            }
            if (dType.IsPhoneNumber)
            {
                return PhoneNumberTemplate;
            }
            if (dType.IsInternet)
            {
                return InternetTemplate;
            }
            throw new NotImplementedException("Not implemented yet");
        }
        return null;
    }
}

Result:

_elmishSample

If something goes wrong I can open PR to your repo

@awaynemd
Copy link
Author

awaynemd commented Sep 5, 2020

@FoggyFinder OMG! It works! I tried for two weeks to figure this out and got nowhere. I can't thank you enough! It will take me another two weeks to completely understand it--there is much to learn here.

BTW, how did you get a movie of the result and upload it to this site? Honors to you sir, thanks for the help.

For any newbie, this is a good example of all (or most) of the mistakes a newbie can make and the great help the people here can be. (Learning github, learning F#, learning "repositories", learning elmish.wpf--this "issue" covered much. To all those in power, it would be a good learning example for other newbies like myself).

@FoggyFinder
Copy link

OMG! It works! I tried for two weeks to figure this out and got nowhere. I can't thank you enough! It will take me another two weeks to completely understand it--there is much to learn here.

You're welcome. Feel free to ask any Qs.

There are a lot of resources that can speed up learning curve.

For WPF-related things there is SO chat room

WPF-room

I got help there many times. Wonderful place but the chat is mostly inactive on weekend.

For F#:

"Central" place is F# Slack

Also there is a community forum F# Forum. Not very active now but I hope it changes one day.

There is also room among SO-chats: F# room

And two really new but perspective Q&A sites CoDidact and TopAnswers.
CoDidact for now use general community - Software Development while TopAnswer has its own F# sub-community. For obvious reasons there are only few members so far.

I didn't mention F# sub-reddit and other popular places like SO because I don't check them. But I'm sure you can get help there as well.

BTW, how did you get a movie of the result and upload it to this site?

I use ScreenToGif

For the second part:

File attachments on issues and pull requests


I hope Elmish.WPF maintainers don't mind that my comment contains so many references =)

@awaynemd
Copy link
Author

awaynemd commented Sep 5, 2020

@FoggyFinder Much thanks. Now to figure out how a tab item can itself contain another tab control ( My app has multiple layers). Bye (for now).

@awaynemd awaynemd closed this as completed Sep 5, 2020
@TysonMN
Copy link
Member

TysonMN commented Sep 6, 2020

I hope Elmish.WPF maintainers don't mind that my comment contains so many references =)

Absolutely not! Just the opposite. I am very grateful for all the help you have given @awaynemd. Thank you very much :)

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

4 participants