Xamarin - 如何从 ContentPage 或 ContentPageViewModel 将 属性 注入 ContentView
Xamarin - How do I inject a property into a ContentView from the ContentPage or ContentPageViewModel
更新: 我对此进行了一些更新以删除对错误的引用。 @michal-diviš 对此给出了更正解决方案。然而,我更大的问题仍然存在。
我是 Xamarin 的新手,正在尝试通过制作一个简单的电子邮件客户端来学习。我正在尝试在我创建的 ContentPage 上设置 属性。
设置
MainPage 只是一个包含两列的网格;左侧是收件箱的 CollectionView,右侧是我的自定义 ContentPage MessageDisplayView
。在 CollectionView 中单击电子邮件时,MainPageViewModel
上的 CurrentMessage
属性 将更新为所选项目。
问题
我正在尝试将 属性 MessageDisplayView.Message
绑定到 MainPageViewModel.CurrentMessage
属性,但内容页面从未更新。我尝试过使用和不使用 BindableProperty,以及在搜索 Google 和 Whosebug 时发现的其他想法。
问题
如何设置和更新我希望与 ContentPage 一起使用的 属性?
代码
MainPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:c="Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:vm="clr-namespace:Project.ViewModel"
xmlns:view="clr-namespace:Project.View"
xmlns:fa="clr-namespace:FontAwesome"
x:Class="Project.MainPage">
<ContentPage.BindingContext>
<vm:MainPageViewModel/>
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<ResourceDictionary Source="ResourceDictionaries/EmailResourceDictionary.xaml"/>
</ResourceDictionary>
</ContentPage.Resources>
<Grid x:Name="MainPageGrid">
<!-- other xaml code -->
<view:MessageDisplayView
x:Name="MyDisplayView"
Grid.Column="1"
Message="{Binding CurrentMessage}" <!-- Error -->
/>
</Grid>
</ContentPage>
MainPageViewModel.cs
using MimeKit;
using Project.EmailLogic;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Project.ViewModel
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public class MainPageViewModel: ObservableObject
{
private MimeMessage currentMessage;
public MimeMessage CurrentMessage
{
get => currentMessage;
set => SetProperty(ref currentMessage, value, nameof(MessageDisplayView.Message));
}
public MainPageViewModel()
{
}
}
}
MessageDisplayView.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:view="clr-namespace:Project.View"
xmlns:vm="clr-namespace:Project.ViewModel"
x:DataType="view:MessageDisplayView"
xmlns:fa="clr-namespace:FontAwesome"
x:Class="Project.View.MessageDisplayView">
<ContentView.Content>
<Grid>
<!-- Various standard xaml things, for example... -->
<!-- Subject Line -->
<Label x:Name="SubjectLine"
Grid.Row="1"
Text="{Binding Message.Subject}"
/>
</Grid>
</ContentView.Content>
</ContentView>
MessageDisplayView.xaml.cs
using MimeKit;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Project.View
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MessageDisplayView : ContentView
{
private MimeMessage message;
public MimeMessage Message
{
get
{
return (MimeMessage)GetValue(MessageProperty);
}
set
{
SetValue(MessageProperty, value);
BodyHtmlViewSource.Html = Message.HtmlBody;
}
}
public BindableProperty MessageProperty =
BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView));
public HtmlWebViewSource BodyHtmlViewSource { get; set; }
public MessageDisplayView()
{
InitializeComponent();
}
}
}
修复
是BindableProperty
的定义!
你有(在 MessageDisplayView.xaml.cs):
public BindableProperty MessageProperty = BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView));
你需要像这样 static readonly
:
public static readonly BindableProperty MessageProperty = BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView));
INotifyPropertyChanged 的用法
您的 MainPageViewModel
中的 CurrentMessage
属性 似乎是问题所在。您已将其创建为 BindableProperty
,但是,这意味着要由用户控件而不是视图模型使用。
你需要在视图模型中实现INotifyPropertyChanged
接口并调用属性setter中的PropertyChanged
事件。这样做是为了 UI 会在 CurrentMessage
属性 发生变化时更新它自己。
像这样调整你的MainViewModel.cs:
using MimeKit;
using Project.EmailLogic;
using Xamarin.Forms;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Project.ViewModel
{
public class MainPageViewModel : INotifyPropertyChanged
{
private MimeMessage currentMessage;
public MimeMessage CurrentMessage
{
get => currentMessage;
set {
currentMessage = value;
OnPropertyChanged(nameof(CurrentMessage))
};
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
在这个例子中,我直接在你的视图模型中实现了 INotifyPropertyChanged
,但更好的方法是从已经实现的基础 class 继承,比如这个:ObservableObject from James Montemagno's MVVM Helpers library。生成的视图模型如下所示:
using MimeKit;
using Project.EmailLogic;
using MvvmHelpers;
namespace Project.ViewModel
{
public class MainPageViewModel : ObservableObject
{
private MimeMessage currentMessage;
public MimeMessage CurrentMessage
{
get => currentMessage;
set => SetProperty(ref currentMessage, value);
}
}
}
问题是 BindableObject 没有听到 属性 更改的通知。
解决方案是将 OnPropertyChanged
方法添加到 ContentView
的代码后面,而不是 ContentPageViewModel
。
此“解决方案”正确地更新了代码中的 属性,但没有更新xaml/UI。我认为这可能是一个单独的问题。
起初这让我感到困惑,当@michal-diviš 指出 OnPropertyChanged
调用时,我认为我应该在后面的 ContentView 代码中自己连接事件订阅。但是在偶然发现 this article 之后,我意识到该方法在其他地方是必需的。
我觉得一个主要问题是关于在 elements/UserControls/ContentPages 之间传递数据或属性的信息不多。在过去的两天里,我阅读和观看了相当多的内容在 BindableProperties 上,但很少看到 OnPropertyChanged 的使用或从其他地方更新属性。也许我遗漏了谈论它的地方,或者它比我意识到的更容易或更明显,但事后看来,这似乎应该在每个 BindableProperty 101 中提到。
当然,除了官方文档之外,如果有人知道关于 classes/views/whatever 之间 sharing/binding/updating 属性的好文章或视频,我很乐意查看。
这是最终的工作代码示例:
public partial class MessageDisplayView : ContentView
{
public MimeMessage Message
{
get
{
return (MimeMessage)GetValue(MessageProperty);
}
set
{
SetValue(MessageProperty, value);
}
}
public static BindableProperty MessageProperty =
BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView), new MimeMessage(),
BindingMode.TwoWay);
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == MessageProperty.PropertyName)
{
if(Message != null)
{
// Update ContentView properties and elements.
}
}
}
再次感谢@michal-diviš 的帮助!
更新: 我对此进行了一些更新以删除对错误的引用。 @michal-diviš 对此给出了更正解决方案。然而,我更大的问题仍然存在。
我是 Xamarin 的新手,正在尝试通过制作一个简单的电子邮件客户端来学习。我正在尝试在我创建的 ContentPage 上设置 属性。
设置
MainPage 只是一个包含两列的网格;左侧是收件箱的 CollectionView,右侧是我的自定义 ContentPage MessageDisplayView
。在 CollectionView 中单击电子邮件时,MainPageViewModel
上的 CurrentMessage
属性 将更新为所选项目。
问题
我正在尝试将 属性 MessageDisplayView.Message
绑定到 MainPageViewModel.CurrentMessage
属性,但内容页面从未更新。我尝试过使用和不使用 BindableProperty,以及在搜索 Google 和 Whosebug 时发现的其他想法。
问题
如何设置和更新我希望与 ContentPage 一起使用的 属性?
代码
MainPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:c="Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:vm="clr-namespace:Project.ViewModel"
xmlns:view="clr-namespace:Project.View"
xmlns:fa="clr-namespace:FontAwesome"
x:Class="Project.MainPage">
<ContentPage.BindingContext>
<vm:MainPageViewModel/>
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<ResourceDictionary Source="ResourceDictionaries/EmailResourceDictionary.xaml"/>
</ResourceDictionary>
</ContentPage.Resources>
<Grid x:Name="MainPageGrid">
<!-- other xaml code -->
<view:MessageDisplayView
x:Name="MyDisplayView"
Grid.Column="1"
Message="{Binding CurrentMessage}" <!-- Error -->
/>
</Grid>
</ContentPage>
MainPageViewModel.cs
using MimeKit;
using Project.EmailLogic;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Project.ViewModel
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public class MainPageViewModel: ObservableObject
{
private MimeMessage currentMessage;
public MimeMessage CurrentMessage
{
get => currentMessage;
set => SetProperty(ref currentMessage, value, nameof(MessageDisplayView.Message));
}
public MainPageViewModel()
{
}
}
}
MessageDisplayView.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:view="clr-namespace:Project.View"
xmlns:vm="clr-namespace:Project.ViewModel"
x:DataType="view:MessageDisplayView"
xmlns:fa="clr-namespace:FontAwesome"
x:Class="Project.View.MessageDisplayView">
<ContentView.Content>
<Grid>
<!-- Various standard xaml things, for example... -->
<!-- Subject Line -->
<Label x:Name="SubjectLine"
Grid.Row="1"
Text="{Binding Message.Subject}"
/>
</Grid>
</ContentView.Content>
</ContentView>
MessageDisplayView.xaml.cs
using MimeKit;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Project.View
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MessageDisplayView : ContentView
{
private MimeMessage message;
public MimeMessage Message
{
get
{
return (MimeMessage)GetValue(MessageProperty);
}
set
{
SetValue(MessageProperty, value);
BodyHtmlViewSource.Html = Message.HtmlBody;
}
}
public BindableProperty MessageProperty =
BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView));
public HtmlWebViewSource BodyHtmlViewSource { get; set; }
public MessageDisplayView()
{
InitializeComponent();
}
}
}
修复
是BindableProperty
的定义!
你有(在 MessageDisplayView.xaml.cs):
public BindableProperty MessageProperty = BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView));
你需要像这样 static readonly
:
public static readonly BindableProperty MessageProperty = BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView));
INotifyPropertyChanged 的用法
您的 MainPageViewModel
中的 CurrentMessage
属性 似乎是问题所在。您已将其创建为 BindableProperty
,但是,这意味着要由用户控件而不是视图模型使用。
你需要在视图模型中实现INotifyPropertyChanged
接口并调用属性setter中的PropertyChanged
事件。这样做是为了 UI 会在 CurrentMessage
属性 发生变化时更新它自己。
像这样调整你的MainViewModel.cs:
using MimeKit;
using Project.EmailLogic;
using Xamarin.Forms;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Project.ViewModel
{
public class MainPageViewModel : INotifyPropertyChanged
{
private MimeMessage currentMessage;
public MimeMessage CurrentMessage
{
get => currentMessage;
set {
currentMessage = value;
OnPropertyChanged(nameof(CurrentMessage))
};
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
在这个例子中,我直接在你的视图模型中实现了 INotifyPropertyChanged
,但更好的方法是从已经实现的基础 class 继承,比如这个:ObservableObject from James Montemagno's MVVM Helpers library。生成的视图模型如下所示:
using MimeKit;
using Project.EmailLogic;
using MvvmHelpers;
namespace Project.ViewModel
{
public class MainPageViewModel : ObservableObject
{
private MimeMessage currentMessage;
public MimeMessage CurrentMessage
{
get => currentMessage;
set => SetProperty(ref currentMessage, value);
}
}
}
问题是 BindableObject 没有听到 属性 更改的通知。
解决方案是将 OnPropertyChanged
方法添加到 ContentView
的代码后面,而不是 ContentPageViewModel
。
此“解决方案”正确地更新了代码中的 属性,但没有更新xaml/UI。我认为这可能是一个单独的问题。
起初这让我感到困惑,当@michal-diviš 指出 OnPropertyChanged
调用时,我认为我应该在后面的 ContentView 代码中自己连接事件订阅。但是在偶然发现 this article 之后,我意识到该方法在其他地方是必需的。
我觉得一个主要问题是关于在 elements/UserControls/ContentPages 之间传递数据或属性的信息不多。在过去的两天里,我阅读和观看了相当多的内容在 BindableProperties 上,但很少看到 OnPropertyChanged 的使用或从其他地方更新属性。也许我遗漏了谈论它的地方,或者它比我意识到的更容易或更明显,但事后看来,这似乎应该在每个 BindableProperty 101 中提到。
当然,除了官方文档之外,如果有人知道关于 classes/views/whatever 之间 sharing/binding/updating 属性的好文章或视频,我很乐意查看。
这是最终的工作代码示例:
public partial class MessageDisplayView : ContentView
{
public MimeMessage Message
{
get
{
return (MimeMessage)GetValue(MessageProperty);
}
set
{
SetValue(MessageProperty, value);
}
}
public static BindableProperty MessageProperty =
BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView), new MimeMessage(),
BindingMode.TwoWay);
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == MessageProperty.PropertyName)
{
if(Message != null)
{
// Update ContentView properties and elements.
}
}
}
再次感谢@michal-diviš 的帮助!