Reputation: 41
I have read many other questions here about this problem, but none solved my problem.
I also recently posted here about getting the property on the ContentView to be updated from the parent ContentPage. That is resolved, and I am certain the Property on the ContentView code behind is updating as intended.
While using a custom ContentView MessageDisplayView
, the elements in my XAML (ie: Label, WebView) do not update to match their bound properties in the code behind.
I have one BindableProperty Message
of type MIME (email data object). This property is set when the ContentView is used on a ContentPage <view:MessageDisplayView x:Name="ReadingPane" Message="{Binding CurrentMessage}"/>
. This works correctly.
In my ContentView MessageDisplayView
, I have several Labels and one WebView. I have tried binding these elements to different things, but the UI is never updated.
I have tried:
Message
, ie: <Label x:Name="FromDisplay" Text="{Binding Message.From[0].Name}" />
<Label x:Name="ToDisplay" Text="{Binding ToName}" />
OnPropertyChanged(nameof(ToName));
, as well as the one called for Message
.The only that will work, is if when OnPropertyChanged
is called for Message, I manually update the Label text. But I don't want this. I would prefer if the code behind never dealt with the xaml/view.
I have verified that all the properties in the code behind are being updated correctly. The Intellisense works in xaml, allowing me to fill in the correct properties. Why does the UI never update to reflect the changes?
The ConentView MessageDisplayView.xaml
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:view="clr-Project.View"
x:DataType="view:MessageDisplayView"
x:Class="Project.View.MessageDisplayView">
<ContentView.Content>
<Grid>
<!-- Subject Line -->
<Label x:Name="SubjectLine"
Grid.Row="1"
Text="{Binding Message.Subject}"
/>
<!-- To/From/Date -->
<StackLayout Orientation="Vertical"
Grid.Column="0"
Grid.Row="2"
>
<StackLayout Orientation="Horizontal">
<Label
Text="From:"
/>
<Label
x:Name="FromDisplay"
Text="{Binding FromName}"
/>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label
Text="To:"
/>
<Label
x:Name="ToDisplay"
Text="{Binding ToName}"
/>
</StackLayout>
</StackLayout>
<!-- Body -->
<WebView x:Name="BodyDisplay"
Grid.Row="3"
BackgroundColor="AliceBlue"
Source="{Binding BodyHtmlViewSource}"
/>
</Grid>
</ContentView.Content>
</ContentView>
The ConentView MessageDisplayView.xaml.cs
using MimeKit;
using System.ComponentModel;
using Xamarin.Forms;
namespace Project.View
{
public partial class MessageDisplayView : ContentView, INotifyPropertyChanged
{
public string FromName { get; set; }
public string ToName
{
get
{
return (string)GetValue(ToNameProperty);
}
set
{
SetValue(ToNameProperty, value);
OnPropertyChanged(nameof(ToName));
}
}
public static BindableProperty ToNameProperty =
BindableProperty.Create(nameof(ToName), typeof(string), typeof(MessageDisplayView), "Default To Field",
BindingMode.TwoWay);
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)
{
FromName = Message.From[0].ToString();
ToName = Message.To[0].Name;
OnPropertyChanged(nameof(ToName));
OnPropertyChanged(nameof(FromName));
//SubjectLine.Text = Message.Subject; <--Manually setting does work (when uncommented)
}
}
}
public MessageDisplayView()
{
InitializeComponent();
ToName = "This is a test name"; <-- I never see this
}
}
}
UPDATE
The 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>
<Grid x:Name="MainPageGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Unrelated Code -->
<view:MessageDisplayView x:Name="ReadingPane"
Grid.Column="1"
IsVisible="{Binding DisplayReadingPane}"
Message="{Binding CurrentMessage}"
/>
</Grid>
</ContentPage>
MainPageViewModel.cs
namespace Project.ViewModel
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public class MainPageViewModel : ObservableObject
{
#region Variables
private ObservableCollection<MimeMessage> currentBox;
public ObservableCollection<MimeMessage> CurrentBox
{
get
{
if (currentBox == null)
currentBox = new ObservableCollection<MimeMessage>();
return currentBox;
}
}
private MimeMessage currentMessage;
public MimeMessage CurrentMessage
{
get => currentMessage;
set
{
SetProperty(ref currentMessage, value, nameof(CurrentMessage));
}
}
public static BindableProperty CurrentMessageProperty =
BindableProperty.Create(nameof(CurrentMessage), typeof(MimeMessage), typeof(MainPageViewModel), new MimeMessage(), BindingMode.TwoWay);
}
}
ObservableObject
*namespace Xamarin.CommunityToolkit.ObjectModel
{
public abstract class ObservableObject : INotifyPropertyChanged
{
private readonly DelegateWeakEventManager weakEventManager = new DelegateWeakEventManager();
public event PropertyChangedEventHandler? PropertyChanged
{
add
{
weakEventManager.AddEventHandler(value, "PropertyChanged");
}
remove
{
weakEventManager.RemoveEventHandler(value, "PropertyChanged");
}
}
protected virtual bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName] string? propertyName = "", Action? onChanging = null, Action? onChanged = null, Func<T, T, bool>? validateValue = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
{
return false;
}
if (validateValue != null && !validateValue!(backingStore, value))
{
return false;
}
onChanging?.Invoke();
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = "")
{
weakEventManager.RaiseEvent(this, new PropertyChangedEventArgs(propertyName), "PropertyChanged");
}
}
}
Upvotes: 2
Views: 926
Reputation: 100
Hmm - i can't see where you set the message, but i changed your code a bit. I don't think you want all that inside the OnPropertyChanged method.. You could give it a try:
public partial class MessageDisplayView : ContentView, INotifyPropertyChanged
{
public string FromName { get; set; }
public string ToName
{
get
{
return (string)GetValue(ToNameProperty);
}
set
{
SetValue(ToNameProperty, value);
OnPropertyChanged(nameof(ToName));
}
}
public static BindableProperty ToNameProperty =
BindableProperty.Create(nameof(ToName), typeof(string), typeof(MessageDisplayView), "Default To Field",
BindingMode.TwoWay);
public MimeMessage Message
{
get
{
return (MimeMessage)GetValue(MessageProperty);
}
set
{
SetValue(MessageProperty, value);
if (value is MimeMessage message)
{
FromName = message.From[0].ToString();
ToName = message.To[0].Name;
OnPropertyChanged(nameof(ToName));
OnPropertyChanged(nameof(FromName));
}
}
}
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);
}
public MessageDisplayView()
{
InitializeComponent();
ToName = "This is a test name"; // < --I never see this
}
}
Upvotes: 0