首页
视频
资源
登录
原
Xamarin.Forms MVVM 与 XAML(二)
3188
人阅读
2022/4/2 17:23
总访问:
2606731
评论:
0
收藏:
0
手机
分类:
Xamarin
![](https://img.tnblog.net/arcimg/hb/59e2f99dba0340d09e3ea49cc983167f.png) >#Xamarin.Forms MVVM 与 XAML(二) [TOC] ##MVVM简介 tn2>MVVM是Model-View-View-Model的简写。它与常常使用的MVC有些相似。Model表示你的数据,View表示你的用户视图界面。通常我们是使用的XMAL来构建的MVVM的视图。 >### View Model tn2>ViewModel就像你应用程序中的核心一样,它将参与各种Web服务或为你的页面执行任何应用程序逻辑的东西,它也是使一切正常工作的原因。它由几个属性,它们绑定到视图上的UI控件。ViewModel包含所有由UI特定的接口和属性,并由一个 ViewModel 的视图的绑定属性,并可获得二者之间的松散耦合,所以需要在ViewModel 直接更新视图中编写相应代码。 例如:你有一个按钮,一个按钮有一个命令属性,所以当用户点击它时,命令动作会被触发,因此视图模型具有绑定到UI显示。反之,它也可以通过命令对用户对它的操作做出反应。 ![](https://img.tnblog.net/arcimg/hb/3a8cf56a9ce14113bbffacf40292a1fd.png) ## 实验项目 tn2>结合上篇文章的项目来继续我们的代码编写,可在此处参考<a href="https://www.tnblog.net/hb/article/details/7143">上一篇文章</a>。 >### 实验目的 tn2>我们希望在上一个项目的基础之上对Editor编辑器控件实现MVVM的交互。 >### 代码编写 tn2>首先创建一个`MainPageViewModel`的类并且实现`INotifyPropertyChanged`接口,该接口可以通过`PropertyChanged`事件属性来提醒前端视图属性已经更新了。 `AllNotes`是存储Editor所保存文本内容的集合。 `SaveCommand`是保存按钮的触发命令,先将内容保存至`AllNotes`后,再清空Editor中的内容。 `EraseCommand`是清空按钮的命令,主要是清空Editor中的文本内容。 ```csharp public class MainPageViewModel : INotifyPropertyChanged { public MainPageViewModel() { EraseCommand = new Command(() => { TheNote = string.Empty; }); SaveCommand = new Command(() => { AllNotes.Add(TheNote); TheNote = string.Empty; }); } public ObservableCollection<string> AllNotes { get; set; } = new ObservableCollection<string>(); public event PropertyChangedEventHandler PropertyChanged; string theNote; public string TheNote { get => theNote; set { theNote = value; var args = new PropertyChangedEventArgs(nameof(TheNote)); PropertyChanged?.Invoke(this, args); } } public Command SaveCommand { get; } public Command EraseCommand { get; } } ``` tn2>除此之外在UI中的`MainPage.xaml`中的用户界面需要进行一定的更改。 在`ContentPage`中我们首先需要通过`xmlns:local`属性来引用我们的命名空间,一般格式如下,这里我们引用刚写好的`MainPageViewModel`类所在的命名空间。 ```bash xmlns:local="clr-namespace:所引用资源的完整命名空间" ``` tn2>随后通过`ContentPage.BindingContext`标签绑定`MainPageViewModel`到数据上下文中去。 ```xml <ContentPage.BindingContext> <local:MainPageViewModel/> </ContentPage.BindingContext> ``` tn2>将Lable标签删除掉,添加上`CollectionView`集合可视标签,需要注意的是它需要通过`ItemsSource`属性来绑定数据源,这里我们绑定的`AllNotes`Editor中的文本内容集合,格式为:`{Binding [属性名]}`(注意必须是公开的属性)。 在这之下我们还需要定义`CollectionView.ItemTemplate`标签来定义每个Editor中的内容所呈现的模板。 关于CollectionView包括定义要显示的数据及其外观的以下属性: | 属性名 | 类型 | 描述 | | ------------ | ------------ | ------------ | | `ItemsSource` | IEnumerable | 指定要显示的项目的集合,默认值为null。 | | `ItemTemplate` | DataTemplate | 指定要应用于要显示的项目集合中的每个项目的模板。 | tn2>由于`ItemTemplate`是`DataTemplate`类型所以需要在`ItemTemplate`之下定义该标签。然后我们可以定义`StackLayout`标签,像堆栈式的集合一样放入我们的内容,然后通过`Label`标签绑定的我们的内容,设置大小为`Title`,并在`Label`外面嵌入一层`Frame`。目前可以把`Frame`标签想成我们前端使用的`div`,可以进行`padding`、`BorderColor`等调整,后面我们还会讲解到。代码如下所示: ```xml <CollectionView ItemsSource="{Binding AllNotes}" Grid.Row="3" Grid.ColumnSpan="2"> <CollectionView.ItemTemplate> <DataTemplate> <StackLayout> <Frame> <Label Text="{Binding .}" FontSize="Title"/> </Frame> </StackLayout> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> ``` tn2>对了,最重要的我们还需要将`TheNote`绑定至`Editor`编辑器中。 ```xml <Editor Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Placeholder="Enter Note Here" Text="{Binding TheNote}" /> ``` tn2>完整的代码如下所示: ```xml <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:FirstApp" x:Class="FirstApp.MainPage"> <ContentPage.BindingContext> <local:MainPageViewModel/> </ContentPage.BindingContext> <!--创建一个画布--> <Grid> <!--行占4份--> <Grid.RowDefinitions> <!-- Height 行高 --> <RowDefinition Height="*"/> <RowDefinition Height="2*"/> <RowDefinition Height=".5*"/> <RowDefinition Height="2*"/> </Grid.RowDefinitions> <!--列占2份--> <Grid.ColumnDefinitions> <!-- Width 列宽 --> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <!--添加一个图片元素--> <!-- BackgroundColor:背景颜色(深蓝色) Grid.Row:图片占的哪一行 Grid.Column:图片占的哪一列 Grid.ColumnSpan:按照列占领几个(1个或2个) --> <Image Source="logo_xamarin" BackgroundColor="PowderBlue" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" /> <!--添加一个编辑器--> <!-- Placeholder:默认显示文本。 --> <Editor Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Placeholder="Enter Note Here" Text="{Binding TheNote}" /> <!--添加两个按钮--> <!-- Text:文本内容 --> <Button Grid.Row="2" Grid.Column="0" Text="Save" Command="{Binding SaveCommand}" /> <Button Grid.Row="2" Grid.Column="1" Text="Erase" Command="{Binding EraseCommand}" /> <CollectionView ItemsSource="{Binding AllNotes}" Grid.Row="3" Grid.ColumnSpan="2"> <CollectionView.ItemTemplate> <DataTemplate> <StackLayout> <Frame> <Label Text="{Binding .}" FontSize="Title"/> </Frame> </StackLayout> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </Grid> </ContentPage> ``` >### 展示案例效果 ![](https://img.tnblog.net/arcimg/hb/51f7d34448e94ad2afd53d32372de17b.gif) ![](https://img.tnblog.net/arcimg/hb/8c4180cf6d194bca830603b8df1c311d.gif) ##Command 的介绍 tn2>简而言之,Command就是响应用户 键盘快捷键输入或者控件事件 (如button点击, 工具条,菜单栏等等), 从而完成如复制、黏贴、打印等操作的一个过程。 >### Command 内部结构 ![](https://img.tnblog.net/arcimg/hb/eae9b9b4efc94ec5a298af925b7f883f.png) tn2>`Commands`本身什么也不做,它底层由`ICommand`组成,包含两个方法(`Execute`/`CanExecute`)和一个事件(`CanExecuteChanged`).要执行实际的action,需要将command和你的代码关联起来,这就是`Command bindings`的作用. 关于其中方法与属性的作用,如下表所示。 | 属性与方法 | 描述 | | ------------ | ------------ | | `_canExecute` | 表示该命令是否生效的委托。 | | `_execute` | 表示传入需要执行命令的委托。 | | `_weakEventManager` | WeakEventManager一种弱事件的管理器,其实内部是由一种键值对的方式存储事件。(后面还会讲到) | | `CanExecuteChanged` | 这个方法表示判断该命令是否有效时之前所触发的事件,该事件是由WeakEventManager来管理的,键名为:CanExecuteChanged | | `CanExecute` | 判断该命令是否有效,返回bool值 | | `Execute` | 立即调用该命令 | | `ChangeCanExecute` | 处理命令是否生效前所调用的方法 | tn2>关于构造,第一个参数表示我们要执行的命令代码,第二个参数表示该命令是否可以执行。 举例:当我们在`EraseCommand`命令中传入第二个委托(也就是`_canExecute`),返回值为`false`它将不会执行该命令。 (如下图所示根本点都点不了) ```csharp EraseCommand = new Command(() => { TheNote = string.Empty; },()=> { return false; }); ``` ![](https://img.tnblog.net/arcimg/hb/36a8eaea27ba444ca42fbf08f6993816.png) >###扩展:讲讲WeakEventManager tn2>平时我们用事件的时候呢,用得不好会导致内存泄漏。 举例:如下面的代码所示。 ```csharp public class FootEventArgs: EventArgs { } public class FootManager { public event EventHandler<FootEventArgs> FootSignalChanged; } ``` ```csharp public class MyClass { public FootManager _footManager; public MyClass(FootManager footManager) { footManager.FootSignalChanged += OnFootChanged; _footManager = footManager; } private void OnFootChanged(object sender, FootEventArgs e) { // 你的代码 } } ``` ```csharp public void DoMaster(FootManager footManager) { var myClass = new MyClass(footManager); myClass.DoSomething(); } ``` tn2>如果`FootManager`这个对象它与应用程序的生命周期一样,也就是与应用程序同生共死。再执行`DoMaster`方法后,`MyClass`的一个实例被创建并且不再使用。但我们的GC并不会收集它,因为`FootManager`中的事件`FootSignalChanged`有对`MyClass`中的`OnFootChanged`方法有所引用,所以会导致我们的**内存泄漏**。而且GC永远不会收集`MyClass`。 普通的处理方法是:实现IDisposable接口并取消引用的事件。 ```csharp public class MyClass: IDisposable { public FootManager _footManager; public MyClass(FootManager footManager) { footManager.FootSignalChanged += OnFootChanged; _footManager = footManager; } private void OnFootChanged(object sender, FootEventArgs e) { // 你的代码 } public void Dispose() { _footManager.FootSignalChanged -= OnFootChanged; } } ``` tn2>当然还可以写个方法取消该事件的引用,也是可以的。接下来我们讲讲弱事件。 tn> 弱事件任然可以完美的解决这个问题。 tn2>前面存在内存泄漏的时候,应用程序告诉GC**这是必需品你不可以回收**. 弱引用/弱事件表示,应用程序告诉GC**我不太需要它,如果我在使用你不要回收,如果没用了你可以随意回收** 它是使用 .NET 的`WeakReference`类实现的,也被我们称为事件聚合器。 如下代码所示便可以解决内存泄漏的问题。 ```csharp public class MyClass { public MyClass(FootManager footManager) { footManager.FootSignalChanged += new WeakEventHandler<FootManager>(OnFootChanged).Handler;; } private void OnFootChanged(object sender, WifiEventArgs e) { // 你的代码 } } ``` tn2>而我们的`WeakEventManager`就是弱引用处理程序的实现之一。WPF 使用类内置了对侦听器端弱事件的支持`WeakEventManager`。它的工作方式类似于以前的包装器解决方案,不同之处在于单个`WeakEventManager`实例充当多个发送者和多个侦听器之间的包装器。由于这个单一实例,`WeakEventManager`当事件从不被调用时,可以避免泄漏:在 a 上注册另一个事件WeakEventManager可以触发对旧事件的清理。这些清理是使用 WPF 调度程序安排的,它们只会发生在运行 WPF 消息循环的线程上。 ![](https://img.tnblog.net/arcimg/hb/f82b5a4641dd4d96b5b78036b13fbac4.png) tn>简单来说就是一个单例,将事件注册到了键值对中,然后每一次调用事件时都会去检查软引用有没有,如果没有将会被清理。 Key:事件名 Value.Subscriber.Target :可获取当前委托在其上调用实例方法的类实例。(`Value.Subscriber.Target`) Value.Handler :事件调用的方法。(`Value.Handler`) tn2>此外,它WeakEventManager还有一个我们以前的解决方案没有的限制:它需要正确设置 sender 参数。如果您使用它附加到button.Click,则只会传递带有的事件sender==button。一些事件实现可能只是将处理程序附加到另一个事件: ```csharp public event EventHandler Event { add { anotherObject.Event += value; } remove { anotherObject.Event -= value; } } ``` tn2>此类事件不能与 一起使用WeakEventManager。 每个事件有一个`WeakEventManager`类,每个线程都有一个实例。定义这些事件的推荐模式是大量样板代码。 幸运的是,我们可以使用泛型来简化它: ```csharp public sealed class ButtonClickEventManager : WeakEventManagerBase<ButtonClickEventManager, Button> { protected override void StartListening(Button source) { source.Click += DeliverEvent; } protected override void StopListening(Button source) { source.Click -= DeliverEvent; } } ``` tn>注意DeliverEvent需要(object, EventArgs),而Click事件提供(object, RoutedEventArgs)。虽然委托类型之间没有转换,但 C#在从方法组创建委托时支持逆变。 tn2>文献:https://www.codeproject.com/Articles/29922/Weak-Events-in-C ## ObservableCollection源码分析 ![](https://img.tnblog.net/arcimg/hb/7ba03e00b13d4bcc98eadbd74550d9aa.png) tn2>ObservableCollection是一个集合类,继承`Collection`集合,并且实现了`INotifyCollectionChanged`, `INotifyPropertyChanged`也就是属性通知与集合通知。继承的类与接口意义如下 | 类名 | 描述 | | ------------ | ------------ | | `Collection` | 为泛型集合提供基类。 | | `INotifyCollectionChanged` | 将集合的动态更改通知给侦听器,例如,何时添加和移除项或者重置整个集合对象。 | | `INotifyPropertyChanged` | 向客户端发出某一属性值已更改的通知。 | tn2>所以再ObservableCollection这个类的方法,对数据的操作很少,重点放在了当自己本事变化的时候(不管是属性,还是集合)会调用发出通知的事件。一般用于更新UI。 >### Add方法源码分析 tn2>我们来看看当我们添加一个元素,时会发生什么事情。 首先我们调用Add方法时会调用`Collection`父类的方法。 ![](https://img.tnblog.net/arcimg/hb/c5ddfd960ab74ea6a78031796e265ca6.png) tn2>但是它在Add方法中调用了InsertItem方法,并且对该方法进行了重写。 ![](https://img.tnblog.net/arcimg/hb/49fa1e7d7b444651bc622e691b515397.png) tn2>通过ObservableCollection发出添加通知集合事件与属性事件来更新UI,这样的集合我们称为动态数据集合。 >### ObservableCollection与List的关系 tn2>其实在ObservableCollection就是在List的基础上多添加了事件通知,因为在ObservableCollection类中操作元素的仍然是List集合。 ![](https://img.tnblog.net/arcimg/hb/2394bfd73fa94573bf15ca225e1c08ff.png) ## PropertyChangedEventHandler事件处理 tn2>MainPageViewModel类实现了INotifyPropertyChanged接口,同时也实现了属性通知的事件`PropertyChangedEventHandler PropertyChanged`。我们重新赋值`TheNote`时,我们发送了一个更新`TheNote`属性的一个事件通知,此时用户界面将会更新绑定`TheNote`属性的控件。反之我们去掉这段前端将没有任何反应。 下图将展示去掉该代码后,并没有清空前端编辑框。 ```csharp var args = new PropertyChangedEventArgs(nameof(TheNote)); PropertyChanged?.Invoke(this, args); ``` ![](https://img.tnblog.net/arcimg/hb/bd1bdef14f624d02a3c0c9d02a5db2c0.png)
欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739
👈{{preArticle.title}}
👉{{nextArticle.title}}
评价
{{titleitem}}
{{titleitem}}
{{item.content}}
{{titleitem}}
{{titleitem}}
{{item.content}}
尘叶心繁
这一世以无限游戏为使命!
博主信息
排名
6
文章
6
粉丝
16
评论
8
文章类别
.net后台框架
168篇
linux
17篇
linux中cve
1篇
windows中cve
0篇
资源分享
10篇
Win32
3篇
前端
28篇
传说中的c
4篇
Xamarin
9篇
docker
15篇
容器编排
101篇
grpc
4篇
Go
15篇
yaml模板
1篇
理论
2篇
更多
Sqlserver
4篇
云产品
39篇
git
3篇
Unity
1篇
考证
2篇
RabbitMq
23篇
Harbor
1篇
Ansible
8篇
Jenkins
17篇
Vue
1篇
Ids4
18篇
istio
1篇
架构
2篇
网络
7篇
windbg
4篇
AI
18篇
threejs
2篇
人物
1篇
嵌入式
2篇
python
13篇
HuggingFace
8篇
pytorch
9篇
opencv
6篇
最新文章
最新评价
{{item.articleTitle}}
{{item.blogName}}
:
{{item.content}}
关于我们
ICP备案 :
渝ICP备18016597号-1
网站信息:
2018-2024
TNBLOG.NET
技术交流:
群号656732739
联系我们:
contact@tnblog.net
欢迎加群
欢迎加群交流技术