使用WPF和MVVM编辑F#记录
问题描述:
在我的previous question中,我问如何使用WPF和MVVM创建一个可关闭的对话框,用于在F#中添加新的Person记录。现在我的下一步是制作另一个对话框来编辑这些记录。但我还没有制定出如何将现有记录传递给ViewModel并使用它填充对话框的字段。我得到异常,因为F#记录是不可变的,而ViewModel似乎期望一个可变对象。使用WPF和MVVM编辑F#记录
我会告诉你我现有的添加对话框的代码 - 假设编辑对话框看起来是一样的。
这就是人的记录:
type Person = { Name: string; Email: string }
下面是添加对话框中的XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fsxaml="http://github.com/fsprojects/FsXaml"
xmlns:local="clr-namespace:ViewModels;assembly=Test3"
local:DialogCloser.DialogResult="{Binding DialogResult}"
Title="Add Person" Height="150" Width="210" ResizeMode="NoResize" >
<Window.DataContext>
<local:PersonAddVM />
</Window.DataContext>
<StackPanel>
<Grid FocusManager.FocusedElement="{Binding ElementName=_name}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="140" />
</Grid.ColumnDefinitions>
<Label Content="_Name" Target="_name" Grid.Row="0" Grid.Column="0" Margin="2" />
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" x:Name="_name"
Grid.Row="0" Grid.Column="1" Margin="4" />
<Label Content="_Email" Target="_email" Grid.Row="2" Grid.Column="0" Margin="2" HorizontalAlignment="Left" />
<TextBox Text="{Binding Email, UpdateSourceTrigger=PropertyChanged}" x:Name="_email"
Grid.Row="1" Grid.Column="1" Margin="4" />
</Grid>
<UniformGrid Rows="1" Columns="2" VerticalAlignment="Center" Margin="2,20,2,2" >
<Button Content="OK" IsDefault="True" IsEnabled="{Binding IsValid}" Command="{Binding OkCmd}"
HorizontalAlignment="Right" Margin="6,0" Width="50" />
<Button Content="Cancel" IsCancel="True" HorizontalAlignment="Left" Margin="6,0" Width="50" />
</UniformGrid>
</StackPanel>
</Window>
这是它(简体)视图模型:
type PersonAddVM() as self =
inherit DialogVMBase() // ViewModelBase with a DialogResult property
let name = self.Factory.Backing(<@ self.Name @>, "", hasLengthAtLeast 4)
let email = self.Factory.Backing(<@ self.Email @>, "", hasLengthAtLeast 5)
let makePerson() = { Name = name.Value; Email = email.Value }
member self.Name with get() = name.Value and set value = name.Value <- value
member self.Email with get() = email.Value and set value = email.Value <- value
member self.OkCmd = self.Factory.CommandSync(fun() ->
PersonCache.Add (makePerson()) // PersonCache is based on an Observable Dictionary
self.DialogResult <- true)
和附加从PersonList对话框中打开对话框,该对话框列出所有人并具有此ViewModel代码:
type PersonListView = XAML<"PersonListView.xaml">
type PersonAddView = XAML<"PersonAddView.xaml">
module PersonViewHandling =
let OpenList() = DialogHelper.OpenDialog (PersonListView())
let OpenAdd() = DialogHelper.OpenDialog (PersonAddView()) // Calls ShowDialog on the view and handles the result
type PersonListVM() as self =
inherit DialogVMBase()
// I want to use the next 3 lines to access the Person selected in the list,
// to pass it to the Edit dialog
let emptyPerson = { Name = ""; Email = "" }
let selectedPerson = self.Factory.Backing(<@ self.SelectedPerson @>, emptyPerson)
member self.SelectedPerson with get() = selectedPerson.Value and set value = selectedPerson.Value <- value
member self.AddCmd = self.Factory.CommandSync (fun _ -> PersonViewHandling.OpenAdd() |> ignore)
那么我怎样才能使用这种方法(或类似的)打开一个相同的编辑对话框,并使用SelectedPerson填充其字段?
答
您目前的做法是合理的,但我会推荐一项更改。
而不是从你的对话框中的视图构建虚拟机,我会手动构建它。这将允许您选择的人传递到虚拟机,然后可以进行编辑:
type PersonAddVM (initial: Person) as self =
// Then "fill in" based off the selection here...
let name = self.Factory.Backing(<@ self.Name @>, initial.Name, hasLengthAtLeast 4)
// ...
// Add an easy way to fetch the person:
member this.EditedPerson with get() = makePerson()
然后,您可以从XAML中删除VM,构建并从主VM取,
member self.AddCmd =
self.Factory.CommandSync
(fun _ ->
// Build this out manually:
let dlg = PersonAddView()
dlg.DataContext <- PersonAddVM(self.SelectedPerson)
if dlg.ShowDialog() = true then
let newPerson = dlg.Person
// Do something with newPerson here
)
这也消除了对缓存的需求,允许您直接推送选择并获取编辑(并使用它)。
如果你想保持对话框中的“服务”,你也可以很容易地把这个包成一个方法就像你有,但是从对话框返回Person option
,即:
module PersonViewHandling =
let OpenAdd initial =
let vm = PersonAddVM(initial)
let win = PersonAddView(DataContext = vm)
if DialogHelper.OpenDialog (win) then
Some win.Person
else
None
然后,您可以处理结果为Person option
而不是处理对话结果。
听起来你已经有了'PersonAddVM'类的正确方法:创建一个具有可变字段的类,然后将它传递给WPF,以便按照它认为合适的方式进行变异。然后,一旦你知道WPF已经完成了对你的类的变异并且它的字段包含了最终值(例如,当用户在对话框上点击OK),你就可以使用'makePerson'这样的函数从当前值创建一个F#记录字段,并在代码的其余部分使用该不可变记录。 – rmunn