在 XAML 和 F 中创建对话框

螺旋仪参数的 XAML 文件如下。它包括三个用于呼吸描记器参数的文本框和一组三个用于颜色的单选按钮。当我们给单选按钮提供相同的组名时 - 就像我们在这里一样 - 当选择一个时,WPF 处理开/关切换。

<!-- This first part is boilerplate, except for the title, height and width.
     Note that some fussing with alignment and margins may be required to get
     the box looking the way you want it. -->
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Parameters" Height="200" Width="250">
    <!-- Here we define a layout of 3 rows and 2 columns below the title bar -->
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <!-- Define a label and a text box for the first three rows. Top row is
             the integer radius of the outer circle -->
        <StackPanel Orientation="Horizontal" Grid.Column="0" Grid.Row="0" 
                    Grid.ColumnSpan="2">
            <Label VerticalAlignment="Top" Margin="5,6,0,1" Content="R: Outer" 
                   Height="24" Width='65'/>
            <TextBox x:Name="radiusR"  Margin="0,0,0,0.5" Width="120" 
                     VerticalAlignment="Bottom" Height="20">Integer</TextBox>
        </StackPanel>
        <!-- This defines a label and text box for the integer radius of the
             inner circle -->
        <StackPanel Orientation="Horizontal" Grid.Column="0" Grid.Row="1" 
                    Grid.ColumnSpan="2">
            <Label VerticalAlignment="Top" Margin="5,6,0,1" Content="r: Inner" 
                   Height="24" Width='65'/>
            <TextBox x:Name="radiusr"  Margin="0,0,0,0.5" Width="120" 
                     VerticalAlignment="Bottom" Height="20" Text="Integer"/>
        </StackPanel>
        <!-- This defines a label and text box for the float ratio of the inner
             circle radius at which the pen is positioned -->
        <StackPanel Orientation="Horizontal" Grid.Column="0" Grid.Row="2" 
                    Grid.ColumnSpan="2">
            <Label VerticalAlignment="Top" Margin="5,6,0,1" Content="l: Ratio" 
                   Height="24" Width='65'/>
            <TextBox x:Name="ratiol"  Margin="0,0,0,1" Width="120" 
                     VerticalAlignment="Bottom" Height="20" Text="Float"/>
        </StackPanel>
        <!-- This defines a radio button group to select color -->
        <StackPanel Orientation="Horizontal" Grid.Column="0" Grid.Row="3" 
                    Grid.ColumnSpan="2">
            <Label VerticalAlignment="Top" Margin="5,6,4,5.333" Content="Color" 
                   Height="24"/>
            <RadioButton x:Name="buttonBlue" Content="Blue" GroupName="Color" 
                         HorizontalAlignment="Left"  VerticalAlignment="Top"
                         Click="buttonBlueClick"
                         Margin="5,13,11,3.5" Height="17"/>
            <RadioButton x:Name="buttonRed"  Content="Red"  GroupName="Color" 
                         HorizontalAlignment="Left" VerticalAlignment="Top"
                         Click="buttonRedClick"
                         Margin="5,13,5,3.5" Height="17" />
            <RadioButton x:Name="buttonRandom"  Content="Random"  
                         GroupName="Color" Click="buttonRandomClick"
                         HorizontalAlignment="Left" VerticalAlignment="Top"
                         Margin="5,13,5,3.5" Height="17" />
        </StackPanel>
        <!-- These are the standard OK/Cancel buttons -->
        <Button Grid.Row="4" Grid.Column="0" Name="okButton" 
                Click="okButton_Click" IsDefault="True">OK</Button>
        <Button Grid.Row="4" Grid.Column="1" Name="cancelButton" 
                IsCancel="True">Cancel</Button>
    </Grid>
</Window>

现在我们为 Dialog.Box 添加代码。按照惯例,用于处理对话框与程序其余部分的接口的代码名为 XXX.xaml.fs,其中关联的 XAML 文件名为 XXX.xaml。

namespace Spirograph

open System.Windows.Controls

type DialogBox(app: App, model: Model, win: MainWindowXaml) as this =
  inherit DialogBoxXaml()

  let myApp   = app
  let myModel = model
  let myWin   = win
  
  // These are the default parameters for the spirograph, changed by this dialog
  // box
  let mutable myR = 220                 // outer circle radius
  let mutable myr = 65                  // inner circle radius
  let mutable myl = 0.8                 // pen position relative to inner circle
  let mutable myColor = MBlue           // pen color

  // These are the dialog box controls. They are initialized when the dialog box
  // is loaded in the whenLoaded function below.
  let mutable RBox: TextBox = null
  let mutable rBox: TextBox = null
  let mutable lBox: TextBox = null

  let mutable blueButton: RadioButton   = null
  let mutable redButton: RadioButton    = null
  let mutable randomButton: RadioButton = null

  // Call this functions to enable or disable parameter input depending on the
  // state of the randomButton. This is a () -> () function to keep it from
  // being executed before we have loaded the dialog box below and found the
  // values of TextBoxes and RadioButtons.
  let enableParameterFields(b: bool) = 
    RBox.IsEnabled <- b
    rBox.IsEnabled <- b
    lBox.IsEnabled <- b

  let whenLoaded _ =
    // Load and initialize text boxes and radio buttons to the current values in 
    // the model. These are changed only if the OK button is clicked, which is 
    // handled below. Also, if the color is Random, we disable the parameter
    // fields.
    RBox <- this.FindName("radiusR") :?> TextBox
    rBox <- this.FindName("radiusr") :?> TextBox
    lBox <- this.FindName("ratiol")  :?> TextBox

    blueButton   <- this.FindName("buttonBlue")   :?> RadioButton
    redButton    <- this.FindName("buttonRed")    :?> RadioButton
    randomButton <- this.FindName("buttonRandom") :?> RadioButton

    RBox.Text <- myModel.MyR.ToString()
    rBox.Text <- myModel.Myr.ToString()
    lBox.Text <- myModel.Myl.ToString()

    myR <- myModel.MyR
    myr <- myModel.Myr
    myl <- myModel.Myl
  
    blueButton.IsChecked   <- new System.Nullable<bool>(myModel.MyColor = MBlue)
    redButton.IsChecked    <- new System.Nullable<bool>(myModel.MyColor = MRed)
    randomButton.IsChecked <- new System.Nullable<bool>(myModel.MyColor = MRandom)
   
    myColor <- myModel.MyColor
    enableParameterFields(not (myColor = MRandom))

  let whenClosing _ =
    // Show the actual spirograph parameters in a message box at close. Note the 
    // \n in the sprintf gives us a linebreak in the MessageBox. This is mainly
    // for debugging, and it can be deleted.
    let s = sprintf "R = %A\nr = %A\nl = %A\nColor = %A" 
                    myModel.MyR myModel.Myr myModel.Myl myModel.MyColor
    System.Windows.MessageBox.Show(s, "Spirograph") |> ignore
    ()
  
  let whenClosed _ =
    () 
  
  do 
    this.Loaded.Add whenLoaded
    this.Closing.Add whenClosing
    this.Closed.Add whenClosed

  override this.buttonBlueClick(sender: obj, 
                                eArgs: System.Windows.RoutedEventArgs) =
    myColor <- MBlue
    enableParameterFields(true)
    () 
  
  override this.buttonRedClick(sender: obj, 
                               eArgs: System.Windows.RoutedEventArgs) =
    myColor <- MRed      
    enableParameterFields(true)
    () 
  
  override this.buttonRandomClick(sender: obj, 
                                  eArgs: System.Windows.RoutedEventArgs) =
    myColor <- MRandom
    enableParameterFields(false)
    () 
  
  override this.okButton_Click(sender: obj,
                               eArgs: System.Windows.RoutedEventArgs) =
    // Only change the spirograph parameters in the model if we hit OK in the 
    // dialog box.
    if myColor = MRandom
    then myModel.Randomize
    else myR <- RBox.Text |> int
         myr <- rBox.Text |> int
         myl <- lBox.Text |> float

         myModel.MyR   <- myR
         myModel.Myr   <- myr
         myModel.Myl   <- myl
         model.MyColor <- myColor

    // Note that setting the DialogResult to nullable true is essential to get
    // the OK button to work.
    this.DialogResult <- new System.Nullable<bool> true         
    () 

这里的大部分代码都致力于确保 Spirograph.fs 中的 spirograph 参数与此对话框中显示的参数相匹配。请注意,没有错误检查:如果为前两个参数字段中预期的整数输入浮点,程序将崩溃。因此,请在你自己的努力中添加错误检查。

另请注意,禁用参数输入字段,在单选按钮中选择随机颜色。它只是为了展示它是如何完成的。

为了在对话框和程序之间来回移动数据,我使用 System.Windows.Element.FindName() 来查找适当的控件,将其转换为应该控制的控件,然后从中获取相关的设置。控制。大多数其他示例程序使用数据绑定。我没有出于两个原因:第一,我无法弄清楚如何使它工作,其次,当它不起作用时,我没有任何错误信息。也许有人在 StackOverflow 上访问它可以告诉我如何使用数据绑定而不包括一整套新的 NuGet 包。