2016-12-14 204 views
4

在我的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填充其字段?

+5

聽起來你已經有了'PersonAddVM'類的正確方法:創建一個具有可變字段的類,然後將它傳遞給WPF,以便按照它認爲合適的方式進行變異。然後,一旦你知道WPF已經完成了對你的類的變異並且它的字段包含了最終值(例如,當用戶在對話框上點擊OK),你就可以使用'makePerson'這樣的函數從當前值創建一個F#記錄字段,並在代碼的其餘部分使用該不可變記錄。 – rmunn

回答

5

您目前的做法是合理的,但我會推薦一項更改。

而不是從你的對話框中的視圖構建虛擬機,我會手動構建它。這將允許您選擇的人傳遞到虛擬機,然後可以進行編輯:

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而不是處理對話結果。