tnblog
首页
视频
资源
登录

Xamarin.Forms MVVM 与 XAML(二)

4486人阅读 2022/4/2 17:23 总访问:3475800 评论:0 收藏:0 手机
分类: Xamarin

Xamarin.Forms MVVM 与 XAML(二)

MVVM简介


MVVM是Model-View-View-Model的简写。它与常常使用的MVC有些相似。Model表示你的数据,View表示你的用户视图界面。通常我们是使用的XMAL来构建的MVVM的视图。

View Model


ViewModel就像你应用程序中的核心一样,它将参与各种Web服务或为你的页面执行任何应用程序逻辑的东西,它也是使一切正常工作的原因。它由几个属性,它们绑定到视图上的UI控件。ViewModel包含所有由UI特定的接口和属性,并由一个 ViewModel 的视图的绑定属性,并可获得二者之间的松散耦合,所以需要在ViewModel 直接更新视图中编写相应代码。
例如:你有一个按钮,一个按钮有一个命令属性,所以当用户点击它时,命令动作会被触发,因此视图模型具有绑定到UI显示。反之,它也可以通过命令对用户对它的操作做出反应。

实验项目


结合上篇文章的项目来继续我们的代码编写,可在此处参考上一篇文章

实验目的


我们希望在上一个项目的基础之上对Editor编辑器控件实现MVVM的交互。

代码编写


首先创建一个MainPageViewModel的类并且实现INotifyPropertyChanged接口,该接口可以通过PropertyChanged事件属性来提醒前端视图属性已经更新了。
AllNotes是存储Editor所保存文本内容的集合。
SaveCommand是保存按钮的触发命令,先将内容保存至AllNotes后,再清空Editor中的内容。
EraseCommand是清空按钮的命令,主要是清空Editor中的文本内容。

  1. public class MainPageViewModel : INotifyPropertyChanged
  2. {
  3. public MainPageViewModel()
  4. {
  5. EraseCommand = new Command(() => {
  6. TheNote = string.Empty;
  7. });
  8. SaveCommand = new Command(() => {
  9. AllNotes.Add(TheNote);
  10. TheNote = string.Empty;
  11. });
  12. }
  13. public ObservableCollection<string> AllNotes { get; set; } = new ObservableCollection<string>();
  14. public event PropertyChangedEventHandler PropertyChanged;
  15. string theNote;
  16. public string TheNote
  17. {
  18. get => theNote;
  19. set
  20. {
  21. theNote = value;
  22. var args = new PropertyChangedEventArgs(nameof(TheNote));
  23. PropertyChanged?.Invoke(this, args);
  24. }
  25. }
  26. public Command SaveCommand { get; }
  27. public Command EraseCommand { get; }
  28. }


除此之外在UI中的MainPage.xaml中的用户界面需要进行一定的更改。
ContentPage中我们首先需要通过xmlns:local属性来引用我们的命名空间,一般格式如下,这里我们引用刚写好的MainPageViewModel类所在的命名空间。

  1. xmlns:local="clr-namespace:所引用资源的完整命名空间"


随后通过ContentPage.BindingContext标签绑定MainPageViewModel到数据上下文中去。

  1. <ContentPage.BindingContext>
  2. <local:MainPageViewModel/>
  3. </ContentPage.BindingContext>


将Lable标签删除掉,添加上CollectionView集合可视标签,需要注意的是它需要通过ItemsSource属性来绑定数据源,这里我们绑定的AllNotesEditor中的文本内容集合,格式为:{Binding [属性名]}(注意必须是公开的属性)。
在这之下我们还需要定义CollectionView.ItemTemplate标签来定义每个Editor中的内容所呈现的模板。
关于CollectionView包括定义要显示的数据及其外观的以下属性:

属性名 类型 描述
ItemsSource IEnumerable 指定要显示的项目的集合,默认值为null。
ItemTemplate DataTemplate 指定要应用于要显示的项目集合中的每个项目的模板。


由于ItemTemplateDataTemplate类型所以需要在ItemTemplate之下定义该标签。然后我们可以定义StackLayout标签,像堆栈式的集合一样放入我们的内容,然后通过Label标签绑定的我们的内容,设置大小为Title,并在Label外面嵌入一层Frame。目前可以把Frame标签想成我们前端使用的div,可以进行paddingBorderColor等调整,后面我们还会讲解到。代码如下所示:

  1. <CollectionView ItemsSource="{Binding AllNotes}" Grid.Row="3" Grid.ColumnSpan="2">
  2. <CollectionView.ItemTemplate>
  3. <DataTemplate>
  4. <StackLayout>
  5. <Frame>
  6. <Label Text="{Binding .}" FontSize="Title"/>
  7. </Frame>
  8. </StackLayout>
  9. </DataTemplate>
  10. </CollectionView.ItemTemplate>
  11. </CollectionView>


对了,最重要的我们还需要将TheNote绑定至Editor编辑器中。

  1. <Editor Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Placeholder="Enter Note Here" Text="{Binding TheNote}" />


完整的代码如下所示:

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  4. xmlns:local="clr-namespace:FirstApp"
  5. x:Class="FirstApp.MainPage">
  6. <ContentPage.BindingContext>
  7. <local:MainPageViewModel/>
  8. </ContentPage.BindingContext>
  9. <!--创建一个画布-->
  10. <Grid>
  11. <!--行占4份-->
  12. <Grid.RowDefinitions>
  13. <!-- Height 行高 -->
  14. <RowDefinition Height="*"/>
  15. <RowDefinition Height="2*"/>
  16. <RowDefinition Height=".5*"/>
  17. <RowDefinition Height="2*"/>
  18. </Grid.RowDefinitions>
  19. <!--列占2份-->
  20. <Grid.ColumnDefinitions>
  21. <!-- Width 列宽 -->
  22. <ColumnDefinition Width="*"/>
  23. <ColumnDefinition Width="*"/>
  24. </Grid.ColumnDefinitions>
  25. <!--添加一个图片元素-->
  26. <!--
  27. BackgroundColor:背景颜色(深蓝色)
  28. Grid.Row:图片占的哪一行
  29. Grid.Column:图片占的哪一列
  30. Grid.ColumnSpan:按照列占领几个(1个或2个)
  31. -->
  32. <Image Source="logo_xamarin" BackgroundColor="PowderBlue"
  33. Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" />
  34. <!--添加一个编辑器-->
  35. <!--
  36. Placeholder:默认显示文本。
  37. -->
  38. <Editor Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Placeholder="Enter Note Here" Text="{Binding TheNote}" />
  39. <!--添加两个按钮-->
  40. <!--
  41. Text:文本内容
  42. -->
  43. <Button Grid.Row="2" Grid.Column="0" Text="Save" Command="{Binding SaveCommand}" />
  44. <Button Grid.Row="2" Grid.Column="1" Text="Erase" Command="{Binding EraseCommand}" />
  45. <CollectionView ItemsSource="{Binding AllNotes}" Grid.Row="3" Grid.ColumnSpan="2">
  46. <CollectionView.ItemTemplate>
  47. <DataTemplate>
  48. <StackLayout>
  49. <Frame>
  50. <Label Text="{Binding .}" FontSize="Title"/>
  51. </Frame>
  52. </StackLayout>
  53. </DataTemplate>
  54. </CollectionView.ItemTemplate>
  55. </CollectionView>
  56. </Grid>
  57. </ContentPage>

展示案例效果

Command 的介绍


简而言之,Command就是响应用户 键盘快捷键输入或者控件事件 (如button点击, 工具条,菜单栏等等),
从而完成如复制、黏贴、打印等操作的一个过程。

Command 内部结构


Commands本身什么也不做,它底层由ICommand组成,包含两个方法(Execute/CanExecute)和一个事件(CanExecuteChanged).要执行实际的action,需要将command和你的代码关联起来,这就是Command bindings的作用.
关于其中方法与属性的作用,如下表所示。

属性与方法 描述
_canExecute 表示该命令是否生效的委托。
_execute 表示传入需要执行命令的委托。
_weakEventManager WeakEventManager一种弱事件的管理器,其实内部是由一种键值对的方式存储事件。(后面还会讲到)
CanExecuteChanged 这个方法表示判断该命令是否有效时之前所触发的事件,该事件是由WeakEventManager来管理的,键名为:CanExecuteChanged
CanExecute 判断该命令是否有效,返回bool值
Execute 立即调用该命令
ChangeCanExecute 处理命令是否生效前所调用的方法


关于构造,第一个参数表示我们要执行的命令代码,第二个参数表示该命令是否可以执行。
举例:当我们在EraseCommand命令中传入第二个委托(也就是_canExecute),返回值为false它将不会执行该命令。
(如下图所示根本点都点不了)

  1. EraseCommand = new Command(() => {
  2. TheNote = string.Empty;
  3. },()=> {
  4. return false;
  5. });

扩展:讲讲WeakEventManager


平时我们用事件的时候呢,用得不好会导致内存泄漏。
举例:如下面的代码所示。

  1. public class FootEventArgs: EventArgs
  2. {
  3. }
  4. public class FootManager
  5. {
  6. public event EventHandler<FootEventArgs> FootSignalChanged;
  7. }
  1. public class MyClass
  2. {
  3. public FootManager _footManager;
  4. public MyClass(FootManager footManager)
  5. {
  6. footManager.FootSignalChanged += OnFootChanged;
  7. _footManager = footManager;
  8. }
  9. private void OnFootChanged(object sender, FootEventArgs e)
  10. {
  11. // 你的代码
  12. }
  13. }
  1. public void DoMaster(FootManager footManager)
  2. {
  3. var myClass = new MyClass(footManager);
  4. myClass.DoSomething();
  5. }


如果FootManager这个对象它与应用程序的生命周期一样,也就是与应用程序同生共死。再执行DoMaster方法后,MyClass的一个实例被创建并且不再使用。但我们的GC并不会收集它,因为FootManager中的事件FootSignalChanged有对MyClass中的OnFootChanged方法有所引用,所以会导致我们的内存泄漏。而且GC永远不会收集MyClass
普通的处理方法是:实现IDisposable接口并取消引用的事件。

  1. public class MyClass: IDisposable
  2. {
  3. public FootManager _footManager;
  4. public MyClass(FootManager footManager)
  5. {
  6. footManager.FootSignalChanged += OnFootChanged;
  7. _footManager = footManager;
  8. }
  9. private void OnFootChanged(object sender, FootEventArgs e)
  10. {
  11. // 你的代码
  12. }
  13. public void Dispose()
  14. {
  15. _footManager.FootSignalChanged -= OnFootChanged;
  16. }
  17. }


当然还可以写个方法取消该事件的引用,也是可以的。接下来我们讲讲弱事件。

弱事件任然可以完美的解决这个问题。


前面存在内存泄漏的时候,应用程序告诉GC这是必需品你不可以回收.
弱引用/弱事件表示,应用程序告诉GC我不太需要它,如果我在使用你不要回收,如果没用了你可以随意回收
它是使用 .NET 的WeakReference类实现的,也被我们称为事件聚合器。
如下代码所示便可以解决内存泄漏的问题。

  1. public class MyClass
  2. {
  3. public MyClass(FootManager footManager)
  4. {
  5. footManager.FootSignalChanged += new WeakEventHandler<FootManager>(OnFootChanged).Handler;;
  6. }
  7. private void OnFootChanged(object sender, WifiEventArgs e)
  8. {
  9. // 你的代码
  10. }
  11. }


而我们的WeakEventManager就是弱引用处理程序的实现之一。WPF 使用类内置了对侦听器端弱事件的支持WeakEventManager。它的工作方式类似于以前的包装器解决方案,不同之处在于单个WeakEventManager实例充当多个发送者和多个侦听器之间的包装器。由于这个单一实例,WeakEventManager当事件从不被调用时,可以避免泄漏:在 a 上注册另一个事件WeakEventManager可以触发对旧事件的清理。这些清理是使用 WPF 调度程序安排的,它们只会发生在运行 WPF 消息循环的线程上。

简单来说就是一个单例,将事件注册到了键值对中,然后每一次调用事件时都会去检查软引用有没有,如果没有将会被清理。
Key:事件名
Value.Subscriber.Target :可获取当前委托在其上调用实例方法的类实例。(Value.Subscriber.Target)
Value.Handler :事件调用的方法。(Value.Handler)


此外,它WeakEventManager还有一个我们以前的解决方案没有的限制:它需要正确设置 sender 参数。如果您使用它附加到button.Click,则只会传递带有的事件sender==button。一些事件实现可能只是将处理程序附加到另一个事件:

  1. public event EventHandler Event {
  2. add { anotherObject.Event += value; }
  3. remove { anotherObject.Event -= value; }
  4. }


此类事件不能与 一起使用WeakEventManager。
每个事件有一个WeakEventManager类,每个线程都有一个实例。定义这些事件的推荐模式是大量样板代码。
幸运的是,我们可以使用泛型来简化它:

  1. public sealed class ButtonClickEventManager
  2. : WeakEventManagerBase<ButtonClickEventManager, Button>
  3. {
  4. protected override void StartListening(Button source)
  5. {
  6. source.Click += DeliverEvent;
  7. }
  8. protected override void StopListening(Button source)
  9. {
  10. source.Click -= DeliverEvent;
  11. }
  12. }

注意DeliverEvent需要(object, EventArgs),而Click事件提供(object, RoutedEventArgs)。虽然委托类型之间没有转换,但 C#在从方法组创建委托时支持逆变。


文献:https://www.codeproject.com/Articles/29922/Weak-Events-in-C

ObservableCollection源码分析


ObservableCollection是一个集合类,继承Collection集合,并且实现了INotifyCollectionChanged, INotifyPropertyChanged也就是属性通知与集合通知。继承的类与接口意义如下

类名 描述
Collection 为泛型集合提供基类。
INotifyCollectionChanged 将集合的动态更改通知给侦听器,例如,何时添加和移除项或者重置整个集合对象。
INotifyPropertyChanged 向客户端发出某一属性值已更改的通知。


所以再ObservableCollection这个类的方法,对数据的操作很少,重点放在了当自己本事变化的时候(不管是属性,还是集合)会调用发出通知的事件。一般用于更新UI。

Add方法源码分析


我们来看看当我们添加一个元素,时会发生什么事情。
首先我们调用Add方法时会调用Collection父类的方法。


但是它在Add方法中调用了InsertItem方法,并且对该方法进行了重写。


通过ObservableCollection发出添加通知集合事件与属性事件来更新UI,这样的集合我们称为动态数据集合。

ObservableCollection与List的关系


其实在ObservableCollection就是在List的基础上多添加了事件通知,因为在ObservableCollection类中操作元素的仍然是List集合。

PropertyChangedEventHandler事件处理


MainPageViewModel类实现了INotifyPropertyChanged接口,同时也实现了属性通知的事件PropertyChangedEventHandler PropertyChanged。我们重新赋值TheNote时,我们发送了一个更新TheNote属性的一个事件通知,此时用户界面将会更新绑定TheNote属性的控件。反之我们去掉这段前端将没有任何反应。
下图将展示去掉该代码后,并没有清空前端编辑框。

  1. var args = new PropertyChangedEventArgs(nameof(TheNote));
  2. PropertyChanged?.Invoke(this, args);


欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

评价

分布式服务架构微服务架构概念的区别联系

分布式:分散压力。微服务:分散能力。当下理解分布式:不同模块部署在不同服务器上作用:分布式解决网站高并发带来问题集...

jsController中分割字符串的方法

js: varstr=OpenRule; varstrs=newArray(); strs=str.split(&quot;,&quot;); for(vari=0;i&lt;strs.length;i++){ $(&q...

Service-stack.redis配置连接池读写分离(处理并发相关等)

配置连接池与读写分类 //写节点(主节点) List&lt;string&gt;writes=newList&lt;string&gt;(); writes.Add(&quot;123456a...

CSS相对定位绝对定位

一般相对定位和绝对定位可以配合起来使用 例如实现如下的效果 只需要在外层div设置为相对定位,在内部设置为绝对定位就...

C委托事件

1.什么是委托?  委托在C#里的意义和在现实里差不多,从字面意思理解即可。举个例子:领导委托小张去传递个文件,这就是...

asp.net core2.0 依赖注入 AddTransientAddScoped的区别

asp.net core主要提供了三种依赖注入的方式其中AddTransient与AddSingleton比较好区别AddTransient瞬时模式:每次都获取一...

Vue.js+Layer实现表格数据绑定更新

一:使用Vue.js绑定好数据与更新事件 使用v-on绑定好事件,在事件里边直接把该行数据传递进去,在更新方法里边就可以直接...

下划线、换行、回车、空格ASCII码值对照表

下划线,ASCII码95换行 , ASCII码10回车 , ASCII码13空格 , ASCII码32ASCII码表:Bin(二进制)Oct(八进制)Dec(十进制)Hex(...

数据读取器指定的"xx"不兼容。某个类型为"xx"的成员在同名的数据读取器中没有对应的列

报错的地方var result= _db.Database.SqlQuery&lt;SMachine&gt;(sql).FirstOrDefault();经过分析,是因为SqlQuery方法查询...

git 下载提交命令

一.先使用git clone下载一个项目 git clone &#39;项目地址&#39; 这里要注意: clone的项目里边会自带git的一些信息,...

微信开发四 接受用户普通消息回复消息

微信接收用户普通消息的文章可以在官方中直接看微信普通消息分类:接受用户文本消息 与 回复文本信息 注意在接收用户普通...

记忆糖的关系【阅读听力】

Link Between Memory and SugarSugar On The BrainIt’s long been understood that there is a connection between memory...

婚姻心脏健康的关系【阅读听力】

Marriage and Heart HealthPlenty of studies have found that being married is generally good for health. One study ze...

iframe自适应高度配合net core使用

去掉iframe边框frameborder=&quot;0&quot;去掉滚动条scrolling=&quot;no&quot;iframe 自适应高度如果内容是固定的,那么就...

net core中使用url编码解码操作

net core中暂时还没有以前asp.net与mvc中的server对象。获取url的编码与解码操作不能使用以前的server对象来获取。使用的是...

Sqlerver添加用户授权

添加用户安全性--&gt;登录名,然后右键新建登录名就可以了然后填写好相关信息就可以了右键属性,用户映射可以选择该用户可...
这一世以无限游戏为使命!
排名
2
文章
636
粉丝
44
评论
93
docker中Sware集群与service
尘叶心繁 : 想学呀!我教你呀
一个bug让程序员走上法庭 索赔金额达400亿日元
叼着奶瓶逛酒吧 : 所以说做程序员也要懂点法律知识
.net core 塑形资源
剑轩 : 收藏收藏
映射AutoMapper
剑轩 : 好是好,这个对效率影响大不大哇,效率高不高
ASP.NET Core 服务注册生命周期
剑轩 : http://www.tnblog.net/aojiancc2/article/details/167
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术