F#奇妙游(14):F#实现WPF的绑定

WPF中的绑定

绑定在UI开发中是一个非常重要的概念,它可以让我们的UI界面和数据模型之间建立起联系,当数据模型发生变化时,UI界面也会随之变化,反之亦然。这样的好处是显而易见的,我们不需要手动去更新UI界面,而是让数据模型自己去更新UI界面,这样的代码更加简洁,更加易于维护。

在传统的UI开发中,我们需要手动去更新UI界面,这样的代码往往是重复的,而且容易出错。具体的方法是通过输入控件的时间来更新数据模型,然后再通过数据模型来更新UI界面。

在.NET平台的WinForm中,在Java的Swing中,以及传统的C++的MFC中,都是通过这种方式来更新UI界面的。

时间上比较近的UI框架,例如JavaFX、WPF、UWP等,都是通过数据绑定来更新UI界面的。

WPF中的绑定

在这里插入图片描述

WPF中的绑定是通过Binding类来实现的,Binding类的构造函数接受一个字符串参数,这个字符串参数是一个路径,它指定了数据模型中的一个属性,这个属性的值会被绑定到UI界面上。

所以绑定必然包含几个要素:

  • source,提供数据的对象
  • target,使用数据的对象
  • path,数据的路径

根据不同的绑定需求,Binding类的构造函数还接受一个可选的参数,这个参数是一个BindingMode枚举值,它指定了绑定的模式,BindingMode枚举有以下几个值:

  • OneWay:单向绑定,数据模型的属性的值会被绑定到UI界面上,但是UI界面的值不会被绑定到数据模型上。
  • TwoWay:双向绑定,数据模型的属性的值会被绑定到UI界面上,UI界面的值也会被绑定到数据模型上。
  • OneWayToSource:单向绑定,UI界面的值会被绑定到数据模型上,但是数据模型的属性的值不会被绑定到UI界面上。
  • OneTime:单次绑定,数据模型的属性的值会被绑定到UI界面上,但是这个绑定只会发生一次,之后数据模型的属性的值的变化不会被绑定到UI界面上。
  • Default:默认绑定,这个值会根据绑定的目标来确定,如果绑定的目标是UI界面,那么就是OneWay,如果绑定的目标是数据模型,那么就是OneWayToSource。

绑定源的实现

在实现绑定的底层中,实际上还是通过事件来完成的。当数据模型的属性的值发生变化时,会触发一个事件,这个事件会被绑定目标监听到,然后绑定目标就会更新UI界面。

在.NET平台中,这个事件是PropertyChanged事件,它是INotifyPropertyChanged接口的一个事件,这个接口定义了一个PropertyChanged事件,这个事件的参数是一个PropertyChangedEventArgs对象,这个对象包含了一个PropertyName属性,这个属性指定了发生变化的属性的名称。

因此,要实现一个源,就需要实现INotifyPropertyChanged接口,然后在属性的setter中触发PropertyChanged事件。

绑定目标的实现

在.NET平台中,绑定目标是一个依赖属性,它是DependencyObject类的一个属性,这个类定义了一个SetValue方法和一个GetValue方法,这两个方法分别用于设置属性的值和获取属性的值。

在WPF中,依赖属性的名称是以“Property”结尾的,例如TextBlock类的Text属性,它的依赖属性的名称是TextProperty。

F#奇妙游

理论的知识就是上面那些,下面我们用F#实现一个WPF应用程序,这个程序就是点击计数(真是无聊啊!)。

在这里插入图片描述

创建项目

首先,我们创建一个F#的WPF应用程序,这个应用程序的名称是FSharpWpfApp1。

dotnet new console -lang F# -o FSharpWpfApp1

把对应的工程文件打开,改成大概是下面的样子。

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net7.0-windows</TargetFramework><UseWpf>true</UseWpf><ApplicationIcon>fsharp.ico</ApplicationIcon></PropertyGroup><ItemGroup><Compile Include="Program.fs" /><Content Include="fsharp.ico" /></ItemGroup></Project>

数据模型

然后,我们创建一个数据模型,这个数据模型包含一个整数属性,这个整数属性的值会被绑定到UI界面上。

type Model() =let mutable count = 0member this.Countwith get() = countand set(value) = count <- value

这个模型要改成是为了支持绑定的,所以我们需要实现INotifyPropertyChanged接口。那么我们定义一个ViewModel的基类。这个基类首先定义一个propertyChanged的事件,这个在C#中是一个委托,但是在F#中是一个Event。

接下来是一点点F#魔法,就是把一个表达式转换成一个字符串,这个字符串就是属性的名称。关于表达式的内容,我还在学……

接下来是实现接口的语法,interface XXXX with XXXXX,这个语法是F#中的接口实现语法,这个语法的意思是,实现XXXX接口,然后XXXXX是接口中函数的实现。这里就把Event的Publish函数设定为对应PropertyChanged事件的触发函数。

type ViewModelBase() =let propertyChanged = Event<_, _>()let toPropName (query: Expr) =match query with| PropertyGet (_, b, _) -> b.Name| _ -> ""interface INotifyPropertyChanged with[<CLIEvent>]member this.PropertyChanged = propertyChanged.Publishabstract member OnPropertyChanged: string -> unitdefault this.OnPropertyChanged(propertyName: string) =propertyChanged.Trigger(this, PropertyChangedEventArgs(propertyName))member this.OnPropertyChanged(expr: Expr) =let propName = toPropName exprthis.OnPropertyChanged(propName)

实际的OnPropertyChanged函数有两个重载,一个是接受字符串参数的,一个是接受表达式参数的。这个表达式参数是用来获取属性名称的,这样就不需要输入属性名称,改为输入获取属性的表达式,下面可以看到例子。

实际的ViewModel类就是继承这个ViewModelBase类,然后实现OnPropertyChanged函数。

type MyData() =inherit ViewModelBase()let mutable count = 0member x.Text = $"计数: %4d{count}"member this.Countwith get () = countand set value =count <- valuethis.OnPropertyChanged(<@ this.Text @>)this.OnPropertyChanged("Count")member this.Increment() = this.Count <- this.Count + 1

可以看到,这里的Count属性的setter中,我们调用了OnPropertyChanged函数。注意,我们这个Model实际上提供了两个可以绑定的属性,一个是Count,一个是Text,但是我们只在Count属性的setter中调用了OnPropertyChanged函数,这是因为Text属性的值是由Count属性的值计算出来的,所以当Count属性的值发生变化时,Text属性的值也会发生变化,所以我们只需要在Count属性的setter中调用OnPropertyChanged函数就可以了。

这里申明属性变化包含了两种方式,一种是直接传递字符串,一种是传递表达式。这两种方式都是可以的,但是传递表达式的方式更加安全,因为它可以在编译时检查属性名称是否正确。

UI界面

UI界面这里还是没有采用XAML的方法实现,而是通过直接编写代码。XAML我还没开始学。

这里的主界面采用Grid布局来实现,首先是行和列的定义,增加两行两列。注意这里对行的高度进行限定,第二行(序号1)的高度是自动,也就是按照空间的需求定义;第一行(序号0)的高度是“*”,也就是占满剩下的全部空间。

let mainContent =let grid = Grid()// define rows and columns for gridgrid.RowDefinitions.Add(RowDefinition(Height = GridLength(1.0, GridUnitType.Star)))grid.RowDefinitions.Add(RowDefinition(Height = GridLength.Auto))grid.ColumnDefinitions.Add(ColumnDefinition())grid.ColumnDefinitions.Add(ColumnDefinition())let textBlock =TextBlock(Foreground = Brushes.Lime,TextAlignment = TextAlignment.Center,HorizontalAlignment = HorizontalAlignment.Center,VerticalAlignment = VerticalAlignment.Center)textBlock.SetBinding(TextBlock.TextProperty,Binding(Source = HelloText, Mode = BindingMode.OneWay, Path = PropertyPath("Text")))|> ignorelet vb = Viewbox(Margin = Thickness(50.0))vb.Child <- textBlockGrid.SetRow(vb, 0)Grid.SetColumn(vb, 0)Grid.SetColumnSpan(vb, 2)grid.Children.Add(vb) |> ignore// add a button to gridlet button =Button(Content = "加一", FontSize = 32.0, FontWeight = FontWeights.Bold, Margin = Thickness(10.0))button.Click.Add(fun _ -> HelloText.Increment())Grid.SetRow(button, 1)Grid.SetColumn(button, 0)grid.Children.Add(button) |> ignore// add a button to gridlet button2 =Button(Content = "清零", FontSize = 32.0, FontWeight = FontWeights.Bold, Margin = Thickness(10.0))button2.Click.Add(fun _ -> HelloText.Count <- 0)Grid.SetRow(button2, 1)Grid.SetColumn(button2, 1)grid.Children.Add(button2) |> ignoregrid

控件的定义很常规,属性可以作为构造函数的参数传递,也可以通过属性赋值的方式传递。唯一比较麻烦的是AttachedProperty的赋值,就是类似于Grid.Row这几个,没办法通过构造函数的参数传递,只能通过Grid.SetRow这样的函数来赋值。

在这里插入图片描述

这里的TextBlock控件的Text属性是通过绑定的方式传递的,这个绑定的源是HelloText,这个HelloText是我们在后面定义的数据模型。这里的绑定的方式是通过Binding类来实现的,Binding类的构造函数接受一个字符串参数,这个字符串参数是一个路径,它指定了数据模型中的一个属性,这个属性的值会被绑定到UI界面上。

主函数

[<STAThread>]
[<EntryPoint>]
let main _ =let app = Application()let window = Window()window.Content <- mainContentwindow.SetBinding(Window.TitleProperty,Binding(Source = HelloText, Mode = BindingMode.OneWay, Path = PropertyPath("Count")))|> ignore// move window to center of screenwindow.WindowStartupLocation <- WindowStartupLocation.CenterScreen// set window sizewindow.Width <- 400.0window.Height <- 300.0app.Run(window) |> ignore0

主函数中把窗口的标题也定义了一个绑定,到HelloText的Count属性上。这样,当HelloText的Count属性的值发生变化时,窗口的标题也会发生变化。

这里隐式包含了一个转换,就是把整数转换成字符串,这个转换是通过ToString函数实现的,这个函数是Object类的一个方法,它可以把任意类型的对象转换成字符串。

全部代码

open System
open System.ComponentModel
open System.Windows
open System.Windows.Controls
open System.Windows.Data
open System.Windows.Mediaopen Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patternstype ViewModelBase() =let propertyChanged = Event<_, _>()let toPropName (query: Expr) =match query with| PropertyGet (_, b, _) -> b.Name| _ -> ""interface INotifyPropertyChanged with[<CLIEvent>]member this.PropertyChanged = propertyChanged.Publishabstract member OnPropertyChanged: string -> unitdefault this.OnPropertyChanged(propertyName: string) =propertyChanged.Trigger(this, PropertyChangedEventArgs(propertyName))member this.OnPropertyChanged(expr: Expr) =let propName = toPropName exprthis.OnPropertyChanged(propName)type MyData() =inherit ViewModelBase()let mutable count = 0member x.Text = $"计数: %4d{count}"member this.Countwith get () = countand set value =count <- valuethis.OnPropertyChanged(<@ this.Text @>)this.OnPropertyChanged("Count")member this.Increment() = this.Count <- this.Count + 1let HelloText = MyData()let mainContent =let grid = Grid()// define rows and columns for gridgrid.RowDefinitions.Add(RowDefinition(Height = GridLength(1.0, GridUnitType.Star)))grid.RowDefinitions.Add(RowDefinition(Height = GridLength.Auto))grid.ColumnDefinitions.Add(ColumnDefinition())grid.ColumnDefinitions.Add(ColumnDefinition())let textBlock =TextBlock(Foreground = Brushes.Lime,TextAlignment = TextAlignment.Center,HorizontalAlignment = HorizontalAlignment.Center,VerticalAlignment = VerticalAlignment.Center)textBlock.SetBinding(TextBlock.TextProperty,Binding(Source = HelloText, Mode = BindingMode.OneWay, Path = PropertyPath("Text")))|> ignorelet vb = Viewbox(Margin = Thickness(50.0))vb.Child <- textBlockGrid.SetRow(vb, 0)Grid.SetColumn(vb, 0)Grid.SetColumnSpan(vb, 2)grid.Children.Add(vb) |> ignore// add a button to gridlet button =Button(Content = "加一", FontSize = 32.0, FontWeight = FontWeights.Bold, Margin = Thickness(10.0))button.Click.Add(fun _ -> HelloText.Increment())Grid.SetRow(button, 1)Grid.SetColumn(button, 0)grid.Children.Add(button) |> ignore// add a button to gridlet button2 =Button(Content = "清零", FontSize = 32.0, FontWeight = FontWeights.Bold, Margin = Thickness(10.0))button2.Click.Add(fun _ -> HelloText.Count <- 0)Grid.SetRow(button2, 1)Grid.SetColumn(button2, 1)grid.Children.Add(button2) |> ignoregrid[<STAThread>]
[<EntryPoint>]
let main _ =let app = Application()let window = Window()window.Content <- mainContentwindow.SetBinding(Window.TitleProperty,Binding(Source = HelloText, Mode = BindingMode.OneWay, Path = PropertyPath("Count")))|> ignore// move window to center of screenwindow.WindowStartupLocation <- WindowStartupLocation.CenterScreen// set window sizewindow.Width <- 400.0window.Height <- 300.0app.Run(window) |> ignore0

总结

  1. F#的WPF开发还是比较麻烦的,特别是跟XAML比,略显繁琐。
  2. 因为F#支持面向对象,所以还是能够比较容易就实现数据绑定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/4349.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity 任意数据在Scene窗口Debug

任意数据在Scene窗口Debug &#x1f354;效果&#x1f96a;食用方法 &#x1f354;效果 如下所示可以很方便的把需要Debug的数据绘制到Scene中&#xff08;普通的Editor脚本只能够对MonoBehaviour进行Debug&#xff09; &#x1f96a;食用方法 &#x1f4a1;. 新建脚本继承Z…

目标检测算法:FPN思想解读

目标检测算法&#xff1a;FPN思想解读 说明 ​ FPN算法一种方法/思想&#xff0c;在许多的模型架构中都经常采用&#xff0c;也是提高模型精度的重要方法。 免责申明 ​ 有误写/错写/错误观点/错误解读&#xff0c;或者大家有其它见解&#xff0c;都可以在评论区指出&#xff0…

chatGPT指令大全可免费使用网站列表chatGPT4试用方案

指令列表 写作助理 &#x1f449; 最常使用的 prompt&#xff0c;用于优化文本的语法、清晰度和简洁度&#xff0c;提高可读性。作为一名中文写作改进助理&#xff0c;你的任务是改进所提供文本的拼写、语法、清晰、简洁和整体可读性&#xff0c;同时分解长句&#xff0c;减少…

手写代码系列

(1)手写clearfix .clearfix:after{content:; display:table;clear:both;} (2) 手写圣杯模型 (3)手写深拷贝 递归 const obj3={age:20,name:xxx,address:{} }, arr:[a,b,c] function deeepClone(obj={}){} (4)手写画图解释原型链(class的原型和本质)

grpc --- protoc生成的pb.go文件的位置

目录 一、环境相关版本二、go_package配置为当前目录下三、go_package配置为指定目录四、结论 一、环境相关版本 go v1.20.5 protoc v4.24.0 protoc-gen-go v1.26.0protoc-gen-go版本过高时需要指定包名&#xff0c;即go_package 二、go_package配置为…

13.postgresql--函数

文章目录 标量示例复合示例有返回值函数返回voidRETURN NEXT ,RETURN QUERYRETURN EXECUTEIF THEN END IFFOREACH,LOOPSLICE &#xff08;1&#xff09;如果函数返回一个标量类型&#xff0c;表达式结果将自动转行成函数的返回类型。但要返回一个复合&#xff08;行&#xff09…

Windows沙盒的安装与配置

沙盒安装 1、打开控制面板 2、选择程序与功能 3、勾选Windows 沙盒&#xff0c;然后点击确定&#xff0c;等待安装完成即可。 沙盒配置 Windows 沙盒支持简单的配置文件&#xff0c;这些文件为沙盒提供最少的自定义参数集。 此功能可与 Windows 10 内部版本 18342 或 Windows…

使用selenium模拟登录解决滑块验证问题

目录 1.登录入口 2.点击“账号密码登录” 3.输入账号、密码并点击登录 4.滑块验证过程 5.小结 本次主要是使用selenium模拟登录网页端的TX新闻&#xff0c;本来最开始是模拟请求的&#xff0c;但是某一天突然发现&#xff0c;部分账号需要经过滑块验证才能正常登录&#x…

python pytest脚本执行工具

pytest脚本执行工具 支持获取当前路径下所有.py脚本 添加多个脚本&#xff0c;一起执行 import tkinter as tk from tkinter import filedialog import subprocess import os from datetime import datetimedef select_script():script_path filedialog.askopenfilename(fil…

windows安装使用 tesseract-ocr

OCR&#xff08;Optical character recognition&#xff0c;光学字符识别&#xff09;是一种将图像中的手写字或者印刷文本转换为机器编码文本的技术。 tesseract-ocr 是由Google开发&#xff0c;支持100多种语言 文档 tessdoc&#xff1a; https://tesseract-ocr.github.io…

浅谈炼钢厂能源计量管理系统的设计与应用

安科瑞 华楠 摘要: 从能源计量和管理的角度&#xff0c;论述了炼钢厂的能源计量管理系统的基本组成及功能。该系统的建立&#xff0c;将使炼钢厂能源介质的计量管理工作实现自动采集、瞬时监测、故障报警、能流监视&#xff1b;完成报表统计、离线输入、成本分析、预测参考等功…

【Java项目实战-牛客社区】--idea创建springboot工程

①. 创建springboot工程&#xff0c;并勾选web开发相关依赖。 。配置Maven ②. 定义Controller类&#xff0c;添加方法 hello。 ③. 运行测试1 使用Spring Initializr方式构建Spring Boot项目 Spring Initializr是一个Web应用&#xff0c;它提供了一个基本的项目结构&#xff…

手机外壳缺陷视觉检测软硬件方案

单独使用一种光源效果图 同轴光会出现亮度不够的情况&#xff1b;回形面光因为光源中间的圆孔会使图像有阴影&#xff0c;造成图像效果不均衡&#xff0c;所以不采用单独光源打光 使用同轴回形面光源效果图 回形光源照亮产品要寻找的边缘&#xff0c;同轴光源起到补光的作用&a…

SpringCloud学习路线(6)—— 远程调用HTTP客户端Feign

一、Feign替代RestTemplate RestTemplate示例 String url "http://userservice/user/" order.getUserId(); User user restTemplate.getForObject(url, User.class);RestTemplate的缺陷&#xff1a; 代码可读性差&#xff0c;编码体验不统一。参数复杂URL难以维…

需求分析案例:全局错误码设计

本文介绍了我在一些业务系统中遇到的错误提示问题&#xff0c;以及进行需求分析和设计实现的过程&#xff0c;欢迎进行交流和指点&#xff0c;一起进步。 1、需求起源 作为程序员&#xff0c;或多或少&#xff0c;都经历过如下场景&#xff1a; 场景1&#xff1a; 产品经理&a…

智慧校园能源管控系统

智慧校园能源管控系统是一种搭载了物联网技术、大数据技术、大数据等技术性智能化能源管理方法系统&#xff0c;致力于为学校提供更高效、安全性、可信赖的能源供应管理和服务。该系统包括了校内的电力工程、水、气、暖等各类能源&#xff0c;根据对能源的实时检测、数据统计分…

文心一言 VS 讯飞星火 VS chatgpt (63)-- 算法导论6.5 2题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;63&#xff09;-- 算法导论6.5 2题 二、试说明 MAX-HEAP-INSERT(A&#xff0c;10)在堆A(15&#xff0c;13&#xff0c;9&#xff0c;5&#xff0c;12&#xff0c;8&#xff0c;7&#xff0c;4&#xff0c;0&#xff0c;6&#xf…

【业务功能篇48】后端接口开发的统一规范

业务背景&#xff1a;日常工作中&#xff0c;我们开发接口时&#xff0c;一般都会涉及到参数校验、异常处理、封装结果返回等处理。而我们项目有时为了快速迭代&#xff0c;在这方面上有所疏忽&#xff0c;后续导致代码维护比较难&#xff0c;不同的开发人员的不同习惯&#xf…

HTTP进化史:从HTTP1的简单到HTTP3的强大

文章目录 &#x1f4c8;I. HTTP1⚡A. 基本特点⚡B. 特点⚡C. 优缺点 &#x1f4c8;II. HTTP2⚡A. 基本特点⚡B. 特点⚡C. 优缺点 &#x1f4c8;III. HTTP3⚡A. 基本特点⚡B. 特点⚡C. 优缺点 &#x1f4c8;IV. 总结&#x1f4c8;附录&#xff1a;「简历必备」前后端实战项目&am…

[RK3568] AMP架构

Rockchip 平台异构多系统 AMP&#xff08;非对称多核架构&#xff09;的开发软件包&#xff0c;支持 Linux(Kernel-4.19)、 Baremetal(HAL)、RTOS(RT-Thread) 组合AMP构建形式。 Baremetal(HAL) Baremetal表示裸机操作系统&#xff0c;HAL是裸机操作系统的一种。 裸机嵌入式系…