F# 以编程方式创建 UserControl:缺少更新(键盘输入似乎已锁定)

F# Creating a UserControl Programmatically: Missing Update (keyboard input seem locked)

我尝试仅使用 F#(版本 12.0.30815.0 + Framework 4.5)创建 "UserControl" "By-Code",因为我更喜欢这个 "way"(C# 和F#).

注意:我必须在输入表单中显示单个 Class 的多个属性,因此此 UserControl 需要实现绑定功能以将 "output" 连接到 Class'属性。

我尝试修改 window 中的数值(即 1200 或 400)或使用键盘上的 TAB 键在控件之间移动焦点,但似乎已锁定。

无法实现数值更新。我只能删除一个或多个单图,没有任何更新。

也许我在 UserControls 中实现的事件 "dataChangedEvent" 中存在概念错误(即 "ucData" 控件的 "Data" 属性 - 请参阅代码).它类似于:"INotifyPropertyChanged".

注意:也许在事件实现中的这个错误也出现在 UserControl 的 "Description" 和 "UnitOfMeasure" 属性中。

有人可以帮我找出下面报告的代码行中 is/are 我的 error/erros 在哪里吗?

非常感谢。

定义用户控件

type ucData() as this =
    inherit Windows.Controls.UserControl()

    static let  OnDataPropertyChanged (sender:DependencyObject) (e:DependencyPropertyChangedEventArgs) =
        let control = unbox<ucData>(sender)
        let newValue = unbox<double>(e.NewValue)
        let oldValue = unbox<double>(e.OldValue)
        System.Console.WriteLine
            (sprintf 
                ">>> OnPropertyChanged 'ucData':'Data': Control Name: %s; Value: %f --> %f <<<" 
                control.Name oldValue newValue )
        let argsEvent = new RoutedPropertyChangedEventArgs<double>(oldValue, newValue)
        argsEvent.RoutedEvent <- ucData.DataChangedEvent  // I get an ERROR here!!!!
        control.RaiseEvent(argsEvent)


    static let OnCoerceDataProperty (sender:DependencyObject) (data:obj) =
        let control = unbox<ucData>(sender)
        let value = unbox<double>(data)
        System.Console.WriteLine
            (sprintf 
                ">>> OnCoerceValue 'ucData':'Data': Control Name: %s; Value: : %f <<<" 
                control.Name value )
        box(value)

    static let OnValidateDataProperty (data:obj) =
        System.Console.WriteLine
            (sprintf 
                ">>> OnValidateValue 'ucData':'Data': Data %s <<<" 
                (data.ToString()) )
        data.GetType() = typeof<double>

    static let dpData = 
        DependencyProperty.Register("Data",typeof<double>, typeof<ucData>, 
            new FrameworkPropertyMetadata( 0.0,
                FrameworkPropertyMetadataOptions.Inherits,
                new PropertyChangedCallback(OnDataPropertyChanged),
                new CoerceValueCallback(OnCoerceDataProperty) ),
            new ValidateValueCallback(OnValidateDataProperty) )

    static let reDataChangedEvent =
        EventManager.RegisterRoutedEvent
            ("DataChanged", RoutingStrategy.Bubble, 
                typeof<RoutedPropertyChangedEventHandler<double>>, typeof<ucData>)

    let dataChangedEvent = 
        let e = new  Event<RoutedPropertyChangedEventHandler<double>,RoutedPropertyChangedEventArgs<double>>() 
        // Equialent to: 
        //    public event RoutedPropertyChangedEventHandler<double> DataChanged
        //    {
        //       add { AddHandler(DataChangedEvent, value); }
        //       remove { RemoveHandler(DataChangedEvent, value); }
        //    }
        // where DataChangedEvent is so defined:
        //    public static readonly RoutedEvent DataChangedEvent;        
        e

    let grid = 
        let c = new Grid()
        let colData = new ColumnDefinition()
        colData.MinWidth <- 70.
        c.ColumnDefinitions.Add( colData )
        c

    let data= 
        let c = new TextBox()
        c.Margin<- new Thickness(3.0)
        c.SetValue(Grid.ColumnProperty,1)
        c

    do        

        grid.Children.Add(data) |> ignore
        this.AddChild(grid) |> ignore

        let bData = new Binding()
        bData.Path <- new PropertyPath("Data")
        bData.StringFormat <- "N"
        bData.ConverterCulture <- System.Globalization.CultureInfo.InvariantCulture
        bData.Mode <- BindingMode.TwoWay 
        bData.RelativeSource <- new RelativeSource(RelativeSourceMode.FindAncestor,typeof<ucData>,1)

        data.SetBinding(TextBox.TextProperty, bData) |> ignore

    static member DataProperty = dpData

    [<Description("Specify the Numerical Data"); Category("UserData")>]
    member x.Data
        with get() = 
            let res = x.GetValue(ucData.DataProperty) 
            (res :?> double)
        and set (v:double) = 
            x.SetValue(ucData.DataProperty, v )

    [<CLIEvent>]
    static member DataChangedEvent with get() = reDataChangedEvent   // I get an ERROR here!!!

    [<CLIEvent>]
    member x.DataChanged = dataChangedEvent.Publish 

创建 WINDOW

    type TestWindow() as this =
        inherit Window()

    let c1 = new ucData()   
    let c2 = new ucData()   

    do
        c1.Name <- "Data1"
        c2.Name <- "Data2"

        this.Width <- 300.
        this.Height <- 300.

        let sp = new StackPanel()

        c1.Data <- 1200.
        c2.Data <- 400.

        sp.Children.Add(c1) |> ignore
        sp.Children.Add(c2) |> ignore

        c1.DataChanged.Add( fun args ->
            MessageBox.Show("The button labeled \"" + c1.Data.ToString()) |>ignore )

        c2.DataChanged.Add( fun args ->
            MessageBox.Show("The button labeled \"" + c1.Data.ToString()) |>ignore )

        this.Content <- sp

运行 测试 WINDOW

let w = new TestWindow()
w.Show()

根据 this 的回答,您需要在 FSI 中启动 WPF 事件循环。由于年纪大了,我不得不稍微调整一下。这是脚本的完整工作版本:

#I @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5"
#r @"WindowsBase.dll"
#r @"PresentationCore.dll"
#r @"PresentationFramework.dll"
#r @"System.Xaml.dll"
#r @"UIAutomationProvider.dll"
#r @"UIAutomationTypes.dll"


module WPFEventLoop =     
    open System    
    open System.Windows    
    open System.Windows.Threading    
    open Microsoft.FSharp.Compiler.Interactive    
    open Microsoft.FSharp.Compiler.Interactive.Settings    

    type RunDelegate<'b> = delegate of unit -> 'b     
    let Create() =         
        let app  =             
            try                 
                // Ensure the current application exists. This may fail, if it already does.                
                let app = new Application() in                 
                // Create a dummy window to act as the main window for the application.                
                // Because we're in FSI we never want to clean this up.                
                new Window() |> ignore;                 
                app              
            with :? InvalidOperationException -> Application.Current        
        let disp = app.Dispatcher        
        let restart = ref false        
        { new IEventLoop with             
            member x.Run() =                    
                app.Run() |> ignore                 
                !restart             

            member x.Invoke(f) =                  
                try 
                    disp.Invoke(DispatcherPriority.Send,new RunDelegate<_>(fun () -> box(f ()))) |> unbox                 
                with e -> eprintf "\n\n ERROR: %O\n" e; reraise()             

            member x.ScheduleRestart() =   ()                 
            //restart := true;                 
            //app.Shutdown()        
         }     

    let Install() = fsi.EventLoop <-  Create()

WPFEventLoop.Install()

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

open System
open System.Windows
open System.Windows.Data 
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media  
open System.ComponentModel 

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

type ucData() as this =
    inherit Windows.Controls.UserControl()

    // ---------------------------------------------------------------------------------------------------------------- //

    static let dpDescription = 
        DependencyProperty.Register("Description", typeof<string>, typeof<ucData>)

    static let dpData = 
        DependencyProperty.Register("Data", typeof<double>, typeof<ucData>)

    static let dpUnitOfMeasure = 
        DependencyProperty.Register("UnitOfMeasure", typeof<string>, typeof<ucData>) 

    // ---------------------------------------------------------------------------------------------------------------- //

    let descriptionChangedEvent = new  Event<RoutedEventHandler, RoutedEventArgs>()

    let dataChangedEvent = new Event<RoutedEventHandler, RoutedEventArgs>()

    let unitOfMeasureChangedEvent = new Event<RoutedEventHandler, RoutedEventArgs>()

    // ---------------------------------------------------------------------------------------------------------------- //

    let grid = 
        let c = new Grid()
        let colDesc = new ColumnDefinition()
        colDesc.MinWidth <- 100.
        colDesc.Width <- System.Windows.GridLength(100.,GridUnitType.Star)
        c.ColumnDefinitions.Add( colDesc )
        let colData = new ColumnDefinition()
        colData.MinWidth <- 70.
        colDesc.Width <- System.Windows.GridLength(70.,GridUnitType.Star)
        c.ColumnDefinitions.Add( colData )
        let colUnit = new ColumnDefinition()
        colUnit.MinWidth <- 70.
        colUnit.Width <- System.Windows.GridLength(70.,GridUnitType.Star)
        c.ColumnDefinitions.Add( colUnit )
        c

    // ---------------------------------------------------------------------------------------------------------------- //

    let desc = 
        let c = new Label()
        c.Margin<- new Thickness(3.0)
        c.SetValue(Grid.ColumnProperty,0)
        c

    // ---------------------------------------------------------------------------------------------------------------- //

    let data= 
        let c = new TextBox()
        c.Margin<- new Thickness(3.0)
        c.SetValue(Grid.ColumnProperty,1)
        c

    // ---------------------------------------------------------------------------------------------------------------- //

    let unit = 
        let c = new Label()
        c.Margin<- new Thickness(3.0)
        c.SetValue(Grid.ColumnProperty,2)
        c

    // ---------------------------------------------------------------------------------------------------------------- //

    let condassign a b c = 
        match a with
        |   true -> b
        | false -> c

    // ---------------------------------------------------------------------------------------------------------------- //

    do        
        grid.Children.Add(desc) |> ignore
        grid.Children.Add(data) |> ignore
        grid.Children.Add(unit) |> ignore
        //this.Content <- grid |> ignore
        this.AddChild(grid) |> ignore

        // ---------------------------------------------------------------------------------------------------------------- //

        this.Data <- 0.00
        this.Description <- "<Description>"
        this.UnitOfMeasure <- "<UM>"

        // ---------------------------------------------------------------------------------------------------------------- //

        this.DataContext <- this 

        // ---------------------------------------------------------------------------------------------------------------- //

        let bDesc = new Binding()
        bDesc.Path <- new PropertyPath("Description")
        bDesc.Mode <- BindingMode.OneWay
        bDesc.RelativeSource <- new RelativeSource(RelativeSourceMode.FindAncestor,typeof<ucData>,1)

        let bData = new Binding()
        bData.Path <- new PropertyPath("Data")
        bData.Mode <- BindingMode.TwoWay 
        bData.RelativeSource <- new RelativeSource(RelativeSourceMode.FindAncestor,typeof<ucData>,1)

        let bUnit = new Binding()
        bUnit.Path <- new PropertyPath("UnitOfMeasure")
        bUnit.Mode <- BindingMode.OneWay
        //bUnit.RelativeSource <- new RelativeSource(RelativeSourceMode.FindAncestor,typeof<ucData>,1) 
        bUnit.Source <- this

        // ---------------------------------------------------------------------------------------------------------------- //

        desc.SetBinding(Label.ContentProperty, bDesc) |> ignore
        data.SetBinding(TextBox.TextProperty, bData) |> ignore
        unit.SetBinding(Label.ContentProperty, bUnit) |> ignore

    // ---------------------------------------------------------------------------------------------------------------- //

    static member DescriptionProperty = dpDescription

    [<Description("Description of the Numerical Data"); Category("UserData")>]
    member x.Description
        with get() =
            let res = x.GetValue(ucData.DescriptionProperty) 
            (res :?> string)
        and set (v:string) = 
            x.SetValue(ucData.DescriptionProperty, (condassign (v=null) " " v) )

    // ---------------------------------------------------------------------------------------------------------------- //

    [<CLIEvent>]
    member x.DescriptionChanged = descriptionChangedEvent.Publish 

    static member DescriptionChangedEvent =
        EventManager.RegisterRoutedEvent
            ("DescriptionChanged", RoutingStrategy.Bubble, 
                typeof<RoutedEventHandler>, typeof<ucData>)

    member x.OnDescriptionChangedEvent() =
        let argsEvent = new RoutedEventArgs()
        argsEvent.RoutedEvent <- ucData.DescriptionChangedEvent 
        argsEvent.Source <- x
        descriptionChangedEvent.Trigger(this, argsEvent)

    // ---------------------------------------------------------------------------------------------------------------- //

    static member DataProperty =dpData

    [<Description("Specify the Numerical Data"); Category("UserData")>]
    member x.Data
        with get() = 
            let res = x.GetValue(ucData.DataProperty) 
            (res :?> double)
        and set (v:double) = 
            x.SetValue(ucData.DataProperty, v )

    // ---------------------------------------------------------------------------------------------------------------- //

    [<CLIEvent>]
    member x.DataChanged = dataChangedEvent.Publish 

    static member DataChangedEvent =
        EventManager.RegisterRoutedEvent
            ("DataChanged", RoutingStrategy.Bubble, 
                typeof<RoutedEventHandler>, typeof<ucData>)

    member x.OnDataChangedEvent() =
        let argsEvent = new RoutedEventArgs()
        argsEvent.RoutedEvent <- ucData.DataChangedEvent 
        argsEvent.Source <- x
        dataChangedEvent.Trigger(this, argsEvent)

    // ---------------------------------------------------------------------------------------------------------------- //

    static member UnitOfMeasureProperty = dpUnitOfMeasure

    [<Description("Specify the 'Unit of Measure'"); Category("UserData")>]
    member x.UnitOfMeasure
        with get() = 
            let res = x.GetValue(ucData.UnitOfMeasureProperty) 
            (res :?> string)
        and set (v:string) = 
            x.SetValue(ucData.UnitOfMeasureProperty, (condassign (v=null) " " v) )

    // ---------------------------------------------------------------------------------------------------------------- //

    [<CLIEvent>]
    member x.UnitOfMeasureChanged = unitOfMeasureChangedEvent.Publish 

    static member UnitOfMeasureChangedEvent=
        EventManager.RegisterRoutedEvent
            ("UnitOfMeasureChanged", RoutingStrategy.Bubble, 
                typeof<RoutedEventHandler>, typeof<ucData>)

    member x.OnUnitOfMeasureChangedEvent() =
        let argsEvent = new RoutedEventArgs()
        argsEvent.RoutedEvent <- ucData.UnitOfMeasureChangedEvent
        argsEvent.Source <- x
        unitOfMeasureChangedEvent.Trigger(this, argsEvent)

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

type TestWindow() as this =
    inherit Window()

    let c1 = new ucData()   
    let c2 = new ucData()   

    do
        this.Width <- 300.
        this.Height <- 300.

        let sp = new StackPanel()

        c1.Description <- "Pippo"
        c1.Data <- 1200.
        c1.UnitOfMeasure <- "mm"
        c1.BorderThickness <- new Thickness(2.0)

        c2.Description <- "Pippo2"
        c2.Data <- 400.
        c2.UnitOfMeasure <- "MPa"
        c2.BorderThickness <- new Thickness(2.0)

        sp.Children.Add(c1) |> ignore
        sp.Children.Add(c2) |> ignore

        this.Content <- sp

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

let w = new TestWindow()
w.Show()