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,一经查实,立即删除!

相关文章

Vue试听本地磁盘的音频

Vue试听本地磁盘的音频 问题描述&#xff1a; 项目中涉及到一个报警声音选择&#xff0c; 有一个试听的功能&#xff0c; 试听后觉得可以才把file文件传给服务端&#xff0c;需要前端自己实现试听本地磁盘的音频&#xff1b; 主要代码如下&#xff1a; <template><di…

Unity 任意数据在Scene窗口Debug

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

MongoDb基本使用

MongoDB基本使用 Nosql简介 在现代的计算系统上每天网络上都会产生庞大的数据量&#xff0c; 这些数据有很大一部分是由关系数据库管 理系统&#xff08;RDBMS&#xff09;来处理。 1970年 E.F.Codd’s提出的关系模型的论文 “A relational model of data for large shared d…

目标检测算法: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配置为…

go time

常用标准库 时间 Go语言没有采用%Y%m%d这样的格式化符号&#xff0c;它很特别。 记住一个字符串"010203040506pm-0700"&#xff0c;即 1月2日下午3点4分5秒06年西7区 &#xff0c;改成我们习惯的格式 符 2006/01/02 15:04:05 -0700 &#xff0c;也不是特别好记&…

13.postgresql--函数

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

[javascript核心-09] 彻底解决js中的类型检测方案

typeof 基于数据类型的值(二进制)进行检测返回结果为字符串typeof NaN结果为numbertypeof null结果为Object.对象存储以000开头&#xff0c;而null也是如此。typeof不能细分对象&#xff0c;结果都是Objecttypeof function(){}结果为function instanceof 检测某个构造函数是…

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…

wpf中窗体的移动通用解决方法

需求背景&#xff1a;设置了不允许改变窗口大小(在Window标签中设置ResizeMode为NoResize)&#xff0c;之后窗口无法被拖动 1.在Window标签中添加’MouseLeftButtonDown‘&#xff0c;并且生成事件处理程序 2.到后台的相应事件处理程序中添加 base.OnMouseLeftButtonDown(e); …

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…

ChatGPT的使用指南:探索与利用先进对话模型的技巧与方法

ChatGPT是一种先进的对话生成模型&#xff0c;通过模拟自然语言交互&#xff0c;能够与人类用户进行流畅的对话。为了帮助用户更好地利用ChatGPT&#xff0c;本文将介绍ChatGPT的基本用法、优化技巧以及注意事项&#xff0c;以便读者能够最大程度地发挥其潜力。 基本用法 输入…

限时购LimitTimeApplication

目录 1 限时购LimitTimeApplication 1.1 商家删除限时购 1.2 根据商品Id获取一个限时购的详细信息 1.3 获取详情 限时购LimitTimeApplication /// <summary>

SQL小示例

SQL示例 一、前言 安装MySQL 启动MySQL MySQL is configured to only allow connections from localhost by defaultTo connect run:mysql -u rootTo start mysql now and restart at login:brew services start mysql下载客户端工具Sequel Ace并登录 二、示例SQL where过…

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…