Closed minewarriorsSchool closed 2 years ago
If I understand correctly, I think you should be able to create a Cmd
binding for MethodX
. Does that make sense?
If you are still having problems, then share a link to a GitHub repository that reproduces the problem.
It does make sense, but it would be MethodX of the XAML object
So I want to be able to control all the methods actually of that GridControl object in the code. But I can not pass the object through the App.xaml.cs since the NewWindow Example of Elmish creates a fresh window all the time if I am correct.
Can you share a link to a GitHub repository where you access the XAML object of the main window the way you want to access the XAML object of the a second newly created window... and also have a second newly created window in the example? Do you only want one of these second newly created windows open at a time?
Could take some time to set up a repository with a sample project. Will come back to this thread eventually when I have found the time for it!
Have some other priorities in the main time of solving first on the backlog.
Seems to me what you want is to access and use GUI objects the way one does when using code-behind. That doesn't fit well with the Elmish way. I think perhaps this is an XY-problem.
I also use DevExpress, and after some years I tend to stay away from the more complex components they have. One specific reason is that SelectedValue
and SelectedValuePath
are not supported, which doesn't fit well with the Elmish.WPF way, although I've gotten dxg:GridControl
to work without that, sort of. Not always well.
But I don't recall having had to get to the actual dxg:GridControl
to achieve anything. Though I have made some hacky functions to do that sort of thing, as a workaround when nothing else will do.
Seems to me what you want is to access and use GUI objects the way one does when using code-behind. That doesn't fit well with the Elmish way. I think perhaps this is an XY-problem.
I also use DevExpress, and after some years I tend to stay away from the more complex components they have. One specific reason is that
SelectedValue
andSelectedValuePath
are not supported, which doesn't fit well with the Elmish.WPF way, although I've gottendxg:GridControl
to work without that, sort of. Not always well.But I don't recall having had to get to the actual
dxg:GridControl
to achieve anything. Though I have made some hacky functions to do that sort of thing, as a workaround when nothing else will do.
@BentTranberg may I ask how you dynamically bestfit your columns without using the actual dxg:gridcontrol or using any of the methods that is within the object? Or do you not even touch that at all?
For example I have made a custom multi cell edit for which I needed the row handles and values to change the selection. For this I would use methods in the actual gridcontrol which I passed through the constructor of the elmish window. How would you do this without touching the object it's method and only using it properties? I do not see a way how hahaha.
I don't have an opinion on what's the best approach. If your idea works well, then that will perhaps be the easiest.
The MVVM Framework in the DevExpress WPF components has many interesting features. If faced with this challenge, I would perhaps explore several of these to see if they could be of any help. Just as an example; I believe one possibility is that you can indeed call functions of a control through a binding.
Another idea is to create a wrapper or descendant of the dxg:GridControl
, in order to add functionality that fits better with Elmish.WPF
And then there's what I mentioned. Within the binding functions I've used reflection to get to objects. Not sure how popular I would be if I posted the function I use for that here.
Not sure how popular I would be if I posted the function I use for that here.
If you are on the fence, then you should definitely share it.
I am not against "breaking the rules". Consider a comparison with pure and impure code. Pure code is great, but impure code is not evil; sometimes it is necessary. However, impure code is more risky. If most of your code is pure, then it becomes possible to take risks with impure code and manage your total risk.
Furthermore, it is very informative to see what functionality users want especially when this involves breaking the rules. This shows me where the existing API is lacking or completely missing.
This is it. I haven't used it for a very long time, so don't remember exactly what it's about. Perhaps there's another proper way now. I have improved much of my old code, and Elmish.WPF is far better now, so most of these hacks have gone away.
(Thanks cmeeren and tysonmn for all you did, not just coding Elmish.WPF but also to teach us how to use it. And there are some more heroes out there that also contributed and helped people like me from time to time. Thanks to you too.)
[<RequireQualifiedAccess>]
module ElmishWpfHack =
open System
let peekViewModel<'M,'R> (vm: obj) =
if not (isNull vm) then
let t = vm.GetType()
let p = t.GetProperty("CurrentModel", Reflection.BindingFlags.NonPublic ||| Reflection.BindingFlags.Public ||| Reflection.BindingFlags.Instance)
if not (isNull p) then
if p.CanRead then
let value = p.GetValue(vm, null)
match value with
| :? Tuple<'M,'R> as tu -> Some tu
| _ -> None
else
None
else
None
else
None
These are some snippets from elsewhere in my huge project that explains a bit more, hopefully, about my use case.
Two of these one-liners are dead code.
let peek (o: obj) = ElmishWpfHack.peekViewModel<Model,RegisterModel> o
let peekCmdParam (f: RegisterModel -> Msg) (models: obj) = match peek models with Some (_, rm) -> f rm | None -> NoOp
let peekIf (f: RegisterModel -> bool) (models: obj) = match peek models with Some (_, rm) -> f rm | None -> false
but one of them is used like this
"AddModuleRegister" |> Binding.cmdParam (peekCmdParam (fun rm -> AddModuleRegister rm.Id))
"MoveRegisterUp" |> Binding.cmdParam (peekCmdParam (fun rm -> MoveRegisterUp rm.Id))
"MoveRegisterDown" |> Binding.cmdParam (peekCmdParam (fun rm -> MoveRegisterDown rm.Id))
"DeleteRegister" |> Binding.cmdParam (peekCmdParam (fun rm -> DeleteRegister rm.Id))
Somewhere else again
"Rows" |> Binding.subModelSeq ((fun (m: Model) -> m.Rows), (fun r -> r.Guid), fun () ->
[
"Guid" |> Binding.oneWay (fun (_, r) -> r.Guid)
"Knapp" |> Binding.twoWay ((fun (m, r: SorterRow) -> r.Knapp), (fun v (m, r: SorterRow) -> SetRowKnapp (r.Guid, v)))
"Register" |> Binding.twoWay ((fun (m, r: SorterRow) -> r.Register), (fun v (m, r: SorterRow) -> SetRowRegister (r.Guid, v)))
"Lengde1" |> Binding.twoWay ((fun (m, r: SorterRow) -> decimal r.Lengde1), (fun v (m, r: SorterRow) -> SetRowLengde1 (r.Guid, float v)))
"Lengde2" |> Binding.twoWay ((fun (m, r: SorterRow) -> decimal r.Lengde2), (fun v (m, r: SorterRow) -> SetRowLengde2 (r.Guid, float v)))
"Lengde3" |> Binding.twoWay ((fun (m, r: SorterRow) -> decimal r.Lengde3), (fun v (m, r: SorterRow) -> SetRowLengde3 (r.Guid, float v)))
"FuktMin" |> Binding.twoWay (
(fun (m, r: SorterRow) -> match r.FuktMin with None -> "" | Some v -> string v),
(fun (v: string) (m, r: SorterRow) -> SetRowFuktMin (r.Guid, Misc.stringToIntOpt v)))
"FuktMaks" |> Binding.twoWay (
(fun (m, r: SorterRow) -> match r.FuktMaks with None -> "" | Some v -> string v),
(fun (v: string) (m, r: SorterRow) -> SetRowFuktMaks (r.Guid, Misc.stringToIntOpt v)))
])
"SelectedItem" |> Binding.subModelSelectedItem ("Rows", (fun m -> m.SelectedItem), SetSelectedItem)
"AddRowBefore" |> Binding.cmd (fun _ -> AddRowBefore)
"AddRowAfter" |> Binding.cmd (fun _ -> AddRowAfter)
"DeleteSelectedRow" |> Binding.cmdIf ((fun _ -> DeleteSelectedRow), (fun m -> m.SelectedItem.IsSome))
"WhenDropRecord" |> Binding.cmdParam (fun (o: obj) ->
match o with
| :? DevExpress.Xpf.Core.DropRecordEventArgs as a ->
a.Handled <- true
let targetRow = a.TargetRecord |> ElmishWpfHack.peekViewModel<Model, SorterRow> |> Option.map snd |> Option.map (fun tr -> tr.Guid)
match targetRow with
| Some targetRow ->
match a.DropPosition with
| DevExpress.Xpf.Core.DropPosition.Before -> DropBefore targetRow
| DevExpress.Xpf.Core.DropPosition.After -> DropAfter targetRow
| DevExpress.Xpf.Core.DropPosition.Append -> NoDropTarget
| DevExpress.Xpf.Core.DropPosition.Inside -> NoDropTarget
| _ -> NoDropTarget
| None -> NoDropTarget
| _ -> NoDropTarget
|> DropRecord)
"WhenCompleteRecordDragDrop" |> Binding.cmdParam (fun (o: obj) ->
match o with
| :? DevExpress.Xpf.Core.CompleteRecordDragDropEventArgs as a ->
a.Handled <- true // Stop component from handling the reorganization of the items itself. We must do it.
if a.Canceled then
CompleteRecordDragDrop []
elif a.Effects = Windows.DragDropEffects.None then // Workaround for apparent bug.
CompleteRecordDragDrop []
else
let records = a.Records |> Seq.map ElmishWpfHack.peekViewModel<Model, SorterRow> |> Seq.choose id |> Seq.map snd |> Seq.map (fun x -> x.Guid) |> Seq.toList
CompleteRecordDragDrop records
| _ -> CompleteRecordDragDrop [])
and in the corresponding XAML
<dxg:GridControl x:Name="gridControl" MaxHeight="1000" ItemsSource="{Binding Rows}" SelectedItem="{Binding SelectedItem}" SelectionMode="None" ShowBorder="False">
<dxg:GridControl.View>
<dxg:TableView AllowPerPixelScrolling="True" ShowGroupPanel="False" ShowIndicator="True" AllowDragDrop="True" ShowDragDropHint="False" AllowColumnFiltering="False" AllowSorting="False">
<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="DropRecord" Command="{Binding WhenDropRecord}" PassEventArgsToCommand="True" UseDispatcher="True"/>
<dxmvvm:EventToCommand EventName="CompleteRecordDragDrop" Command="{Binding WhenCompleteRecordDragDrop}" PassEventArgsToCommand="True" UseDispatcher="True"/>
</dxmvvm:Interaction.Behaviors>
</dxg:TableView>
</dxg:GridControl.View>
<dxg:GridControl.SortInfo>
<dxg:GridSortInfo FieldName="Nr" SortOrder="Ascending" />
</dxg:GridControl.SortInfo>
<dxg:GridControl.Columns>
<dxg:GridColumn AllowEditing="true" Binding="{Binding Register, Mode=TwoWay}" Header="Register" Width="70"/>
<dxg:GridColumn AllowEditing="true" Binding="{Binding Knapp, Mode=TwoWay}" Header="Knapp" Width="70"/>
<dxg:GridColumn AllowEditing="true" Binding="{Binding Lengde1, Mode=TwoWay}" Header="Lengde 1" Width="70"/>
<dxg:GridColumn AllowEditing="true" Binding="{Binding Lengde2, Mode=TwoWay}" Header="Lengde 2" Width="70"/>
<dxg:GridColumn AllowEditing="true" Binding="{Binding Lengde3, Mode=TwoWay}" Header="Lengde 3" Width="70"/>
<dxg:GridColumn AllowEditing="true" Binding="{Binding FuktMin, Mode=TwoWay}" Header="Fukt min." Width="70"/>
<dxg:GridColumn AllowEditing="true" Binding="{Binding FuktMaks, Mode=TwoWay}" Header="Fukt maks." Width="70"/>
</dxg:GridControl.Columns>
</dxg:GridControl>
And that was all that was left of these hacks in my code. I had at least ten times more of them long ago.
It's not like I believe this is going to solve this issue. I just post it for what it's worth.
I do have some more code that uses dxg:GridControl
, but nothing that I believe can enlighten us with regards to this issue. As already hinted at, I try not to use these complex controls. Instead I've become more of an expert on building stuff from the ground up with the basic WPF controls, and that tends to be a lot more pleasant. It's also surprising how far it gets me. And how much more performant it is, probably only because then I know exactly what I'm doing, so I do it right.
Thanks for sharing all this code @BentTranberg.
Do you think you could also share in a small GitHub repo so I could see it in action?
Or don't bother if yo don't really use these hacks anymore.
OK so I see that my issue was still open. It can be closed. The way a person could pass xaml objects to the elmish loop would be to use the "passEventArgsToCommand" on an event or use the "CommandParameter" to customize it.
So currently I am creating an application which schedules employees. For a custom feature that creates "custom made" projects and employees, I open a new window like Window2 out of the newwindow example. This all works perfectly fine. However, I need to acces a XAML object itself now and have no idea how to do that.
I make use of devexpress and need to acces the 2 gridcontrol objects that are in this window, but I have no idea how I should go by this. There is not really a way to bind to an object and get a reference of an object if I am correct.
In my main window I actually have passed the main gridcontrol of the application in the app.xaml.cs like we do in the newWindow example with the Window.
But since this newWindow "Popup" gets created at run time all the time, I see no way to do the same thing.
Hopefully you guys have an idea on how I could manage this.
Example: ` <dxg:GridControl ItemsSource="{Binding AllAvailableObjectsGrid}" AutoGenerateColumns="AddNew" EnableSmartColumnsGeneration="True" Name="AvailableObjectsGrid" SelectionMode="MultipleRow" ItemsSourceChanged="AvailableProjects_ItemsSourceChanged" MaxWidth="500" HorizontalAlignment="Left"
So what I want to achieve is that I can do for this newly generated window is: AvailableObjectsGrid.MethodX().
thanks in advance for your help, time and consideration!