(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.
```
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
`
Elmish.WPF returns its internal ViewModel because that's exactly what you are specifying here:
```f#
"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:
```f#
"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.
Please try to follow the steps in https://github.com/elmish/Elmish.WPF/issues/268#issuecomment-678814229 so you can share a branch with this code that has the behavior you describe.
@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???
We will be better able assist you if you are able to share a branch with this code that has the behavior you describe.
@cmeeren I'm guessing what I need is something that functions like this:
f#
"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
@bender2k14
It's hard for this old dog to learn new tricks.
Here is what I did:
Now what?
How do I make a branch and share it? Thanks.
I really would like to get this tab control working... Thanks.
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?
Following these instructions to
@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.
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?
@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.
Can you change the code in your branch so that it can be immediately executed?
@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.
...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.
@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?
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.
@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.
git and GitHub certainly support folders. The root of this repo contains the folders
.github,logo, andsrc.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.
@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.
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.
@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
@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.:
```fs
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
|> 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:
```fsharp
"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}}">
@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
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!
@awaynemd almost there!
F# part:
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 }
"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)
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:
DataTemplateSelector you don't have to specify DataTypeXAML:
<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:

If something goes wrong I can open PR to your repo
@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).
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
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 =)
@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).
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 :)