MVVM OnpropertyChange UI changes delayed - wpf

i have a comboxbox that while it is beign populated i want it replaced in the UI by a message saying it is being loaded.
i did this by using a textbox showing the message and giving both objects visibility bindings in the view model (IsShowAuthComboBox &LoadingAuthenticationMsg)
here's the XAML code
<ComboBox x:Name="ComboBoxAuthSource"
Grid.Row="3"
Style="{StaticResource ComboBoxStyle}"
SelectedItem ="{Binding SelectedAuthenticationSource,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding AuthenticationSource,UpdateSourceTrigger=PropertyChanged}"
Visibility= "{Binding IsShowAuthComboBox, Converter={StaticResource BoolToVis}}" />
<TextBox x:Name="ComboBoxAuthCover"
Grid.Row="3" Grid.Column="{StaticResource TableColumn}"
Style="{StaticResource FieldBoxStyle }"
FontSize="12"
IsReadOnly="True"
Visibility="{Binding IsShowGettingAuthenticationMsg, Converter={StaticResource BoolToVis}}"
Text="{Binding LoadingAuthenticationMsg,UpdateSourceTrigger=PropertyChanged,Mode=OneWay,FallbackValue='Loading authentication sources...'}" />
And here's the viewModel
public bool IsShowAuthComboBox
{
set
{
if (_isShowAuthenticationComboBox != value)
{
_isShowAuthenticationComboBox = value;
OnPropertyChanged("IsShowAuthComboBox");
OnPropertyChanged("IsShowGettingAuthenticationMsg");
}
}
get =>_isShowAuthenticationComboBox;
}
public bool IsShowGettingAuthenticationMsg => !_isShowAuthenticationComboBox;
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
Log.Write(LogClass.General, LogLevel.Debug,
$"{propertyName} update triggerd",
_moduleName);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
this code is the first thing that happens in the relevant flow, but i will sometimes only see it at the very end of the execution and for only for an instant.
at other times it will work as expected.
what am i missing here?
EDIT :
this also accurs when validating the IP ,simpler code.
here's the code
public string SelectedServer
{
get => _selectedServer;
set
{
lock (_lockObj)
{
IsShowAuthComboBox = false;
if (!IsValideIp(value))
//some code
IsShowAuthComboBox = true;
}
}
bool IsValideIp(string ip)
{
//some code
//calls the server sync
return RemotingConfigurator.GetServerConfig(ip).isValid;
}

Your issue is that you are setting the IsShowAuthComboBox property and calling the IsValideIp synchronously on the same thread. And a single thread cannot both update the UI and query a database simultaneously.
What you should do is to call the IsValideIp on a background thread. I wouldn't do this in the setter of a property though, but rather in a command. You may want to read #Stephen Cleary's blog post on the subject.

this is what i ended up doing. moved the UI changes away from the data layer and into the viewModel (SetUiOnWait)
public string SelectedServer
{
get => _selectedServer;
set
{
//IsShowAuthComboBox = false;
SetUiOnWait(true);
Log.Write(LogClass.General, LogLevel.Debug,
$"Server changed from {_selectedServer} to {value} by user",
_moduleName);
_selectedServer = value;
OnPropertyChanged();
// OnPropertyChanged();
//workaround for when changing servers when a unique
//authentication source is selected causes the selected source to be null :\
if (AuthenticationSource.Any())
{
SelectedAuthenticationSource = AuthenticationSource[0];
}
Task.Factory.StartNew(() =>
{
LoginInfo.SelectedServer = _selectedServer;
}).ContinueWith((t) =>
{
if(t.Exception !=null)
{
ExceptionLog.Write(t.Exception.GetBaseException(),_moduleName);
}
RefreshAuthenticationProperties();
OnPropertyChanged("IsLimitedClinicalUse");
OnPropertyChanged("IsNotForClinicalUse");
SetUiOnWait(false);
});
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
dispatcher.Invoke((Action)(() =>
{
//PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}));
}
Task.Factory.StartNew() forces and logic to be executed on a new thread and for the UI changes to wait for it be completed.
and invoke within OnPropertyChange forces the event to be handled by the UI thread.

Related

WPF: Changing tabs makes Windowsformshost child disappear

if you can spare the time, I am working on a problem for which I can't find a solution on the internet.
I need two tabs' richtextboxes to bind the same property. Both RichtextBoxes are hosted in WPF via Windowsformshost. But if I alternate between tabs, one RichtTextBox will simply dissapear (always the first one that was visible). I am migrating an app and so far, I am forced to use the Windowsforms RichtextBox.
I hope I managed to properly convey my problem - sorry, I am not a native speaker.
Thanks in advance
Edit:
I was asked to provide a clear example of my problem. Thanks for the note. I completely rewrote my question. Further, I have uploaded a micro app where I have isolated the problem. Just click the two tab buttons alternately and one Richtextbox will dissapear.
Below, I will provide the code if this serves:
This is my Mainwindow (XAML):
<StackPanel Orientation="Horizontal" Height="35" Margin="0,35,0,0" VerticalAlignment="Top" >
<Button x:Name="Tab1" Command="{Binding LeftCommand}" Content="Left" MinWidth="100" />
<Button x:Name="Tab2" Command="{Binding RightCommand}" Content="Right" MinWidth="100" />
</StackPanel>
<Frame x:Name="MyFrame"
Content="{Binding Path=CurrentTab, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="5,70,0,0" NavigationUIVisibility="Hidden" />
This is its viewmodel:
class MainWindowViewModel : INotifyPropertyChanged
{
public ICommand LeftCommand { get; }
public ICommand RightCommand { get; }
private TabViewModel MyTabViewModel { get; set; }
private PageLeft MyPageLeft { get; set; }
private PageRight MyPageRight { get; set; }
public MainWindowViewModel()
{
this.LeftCommand = new ModelCommand(p => this.SetSelectedTab("left"));
this.RightCommand = new ModelCommand(p => this.SetSelectedTab("right"));
this.MyTabViewModel = new TabViewModel();
this.MyPageLeft = new PageLeft() { DataContext = this.MyTabViewModel };
this.MyPageRight = new PageRight() { DataContext = this.MyTabViewModel };
//initial view on something
//this.SetSelectedTab("left");
}
private void SetSelectedTab(string param)
{
switch (param)
{
case "left":
this.CurrentTab = this.MyPageLeft;
break;
case "right":
this.CurrentTab = this.MyPageRight;
break;
}
}
private object _CurrentTab;
public object CurrentTab
{
get { return _CurrentTab; }
set
{
if (value != _CurrentTab)
{
_CurrentTab = value;
NotifyPropertyChanged_MainViewModel();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged_MainViewModel([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Furthermore, I have two pages (MyPageLeft, MyPageRight) that use the same viewmodel (TabViewModel) and use the same bit of XAML code:
<ContentControl Content="{Binding Path=MyWindowsFormsHost, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Both Pages use the same TabViewModel:
class TabViewModel : INotifyPropertyChanged
{
private WindowsFormsHost _MyWindowsFormsHost;
public WindowsFormsHost MyWindowsFormsHost
{
get { return _MyWindowsFormsHost; }
set
{
if (value != _MyWindowsFormsHost)
{
_MyWindowsFormsHost = value;
NotifyPropertyChanged_TabViewModel();
}
}
}
public TabViewModel()
{
this.MyWindowsFormsHost = new WindowsFormsHost() { Child = new RichTextBox() { Text = "test" } };
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged_TabViewModel([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The Problem: If I start the app and click on the two tab buttons alternatingly, one of the framed RichtextBoxes will dissapear.
If anyone might need it, I used a dirty solution - although it might not be recommendable.
I extended the event of the switch tab buttons. It takes the RTF property of the currently selected Tab's Richtextbox and infuses it in the other Richtextbox. It goes kinda like this:
if (Tab2 Button is clicked)
this.MyRTF = Tab1.Richtextbox.RTF;
Tab2.Richttextbox.Rtf = this.MyRTF;
Note that this is a beginner's hack on a probably overall questionable approach.
Thanks to anyone who read my question!

MVVM Binding to 2 controls not working

I'm trying to make a control that has a current value with an optional equation string.
I have 2 textboxes:
One (a) where you can enter an equation shortcut to a value to put into the other (b).
(b) contains the actual value.
(for example, in (a), if you enter 'pi', the second will then fill with "3.1415926535897931")
I'm using 2 textboxes so the user can refine their equation if they need to, and watch the value change as they modify it.
The data has 2 fields, one being the equation string and the other being the current value.
so I have (a).Text bound to the string, a new property on (a) that holds the value, and I bind (b).Text to the value also.
(a).Text is TwoWay
(a).Value is OneWayToSource (since changes to the text should only be pushed to b)
(b).Value is TwoWay
This all works fine if I have the data set in the constructor before any XAML binding, but does not work at all if I add the data after binding.
Here is a minimal amount of code that shows the problem.
The only comment is at the line that can make it work or not.
As a last resort I could turn it into a custom control and handle it in the code-behind, but I'd think this should work in the first place.
Any ideas why this isn't working?
Thanks!
Here is the XAML:
<Window x:Class="twoBindingsOnSameField.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:twoBindingsOnSameField"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Content="load data" Click="Button_Click" Width="80" IsEnabled="{Binding NeedsData}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="enter text:" Width="80"/>
<local:TextBoxCalc Text="{Binding Item.ItemString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextBoxCalculatedValue="{Binding Item.ItemValue, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
Width="200"
IsEnabled="{Binding HasData}"
/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="updated text:" Width="80"/>
<TextBox Text="{Binding Item.ItemValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="200"
IsEnabled="{Binding HasData}"
/>
</StackPanel>
</StackPanel>
</Window>
Here is the codebehind.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace twoBindingsOnSameField
{
public partial class MainWindow : Window
{
data data;
public MainWindow()
{
InitializeComponent();
data = new data();
/// ---- Does not work with the following line commented out, but does if it is uncommented ----
/// ---- use the button to set the data ----
//setdata();
DataContext = data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
setdata();
}
void setdata()
{
if (data.Item == null)
data.Item = new dataitem();
}
}
public class data : notifybase
{
dataitem item;
public data()
{
}
public dataitem Item
{
get
{
return item;
}
set
{
if (item != value)
{
item = value;
notifyPropertyChanged("Item");
notifyPropertyChanged("HasData");
notifyPropertyChanged("NeedsData");
}
}
}
public bool HasData
{
get
{
return Item != null;
}
}
public bool NeedsData
{
get
{
return Item == null;
}
}
}
public class dataitem : notifybase
{
string itemString;
string itemValue;
public dataitem()
{
itemString = "3";
itemValue = "4";
}
public virtual string ItemString
{
get
{
return this.itemString;
}
set
{
if (!object.Equals(this.itemString, value))
{
this.itemString = value;
notifyPropertyChanged("ItemString");
}
}
}
public virtual string ItemValue
{
get
{
return this.itemValue;
}
set
{
if (!object.Equals(this.itemValue, value))
{
this.itemValue = value;
notifyPropertyChanged("ItemValue");
}
}
}
}
public class TextBoxCalc : TextBox
{
public TextBoxCalc()
{
TextProperty.AddHandler(this, (o,e)=>TextBoxCalculatedValue="updated:" + Text);
}
#region TextBoxCalculatedValue
public static DependencyProperty TextBoxCalculatedValueProperty = DependencyProperty.Register("TextBoxCalculatedValue", typeof(string), typeof(TextBoxCalc), new PropertyMetadata(""));
public string TextBoxCalculatedValue
{
get
{
return (string)GetValue(TextBoxCalculatedValueProperty);
}
set
{
if (!object.Equals(TextBoxCalculatedValue, value))
SetValue(TextBoxCalculatedValueProperty, value);
}
}
#endregion
}
public class notifybase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
protected virtual void notifyPropertyChanged(string propertyName)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
OnPropertyChanged(e);
}
}
static class extensions
{
public static void AddHandler(this DependencyProperty prop, object component, EventHandler handler)
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(prop, component.GetType());
if (dpd != null)
dpd.AddValueChanged(component, handler);
}
}
}
The reason why it works when you uncomment //setdata(); is because it is initializing the object in what is effectively your viewmodel, therefore you can change its properties via binding. To clarify as a side note, data would be your view model, and dataitem is your model, however you're dataitem is using INPC, so it doesn't really make sense in this case to have a viewmodel necessarily.
Anyways, the issue is that TextBoxCalculatedValue is set to a OneWayToSource binding. When you run the code commented out, its going to try and bind to a null value. When it does, it tries to update a null value, which isn't possible. WPF handles what would normally be a null exception automatically. When you update the dataItem by clicking the button, it doesn't update the object TextBoxCalc is bound to, so instead, it will continue trying to bind & update the null object. Change it to a TwoWay binding and you'll see a difference. Changing to TwoWay is probably your best option.
Good practice is to use constructor injection to practice dependency injection. With that being said, passing a dataItem to data would be the best route, and at the very least, initializing dataItem in data's constructor would be an ideal approach. So,
public data(dataItem item)
{
Item = item;
}
or
public data()
{
Item = new dataitem();
}

Binding PropertyChanged Dilemma

Oh lord is monday again and I feel like all my knowledge about wpf has been deleted just like that.
I thought when Binding in Mode PropertyChanged that the Source will be only updated when the Target property was changed and not all the time.
Here is an example where Binding keeps updating the Source even though the Target property hasn't been changed. Why?
Btw, I am in .NET 4.0
<StackPanel>
<TextBox x:Name="tbx1" Text="{Binding Txt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="Change Text" Click="OnClick" />
</StackPanel>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
private void OnClick(object sender, RoutedEventArgs e)
{
tbx1.Text = "hello";
}
}
public class ViewModel
{
private string txt;
public string Txt
{
get { return txt; }
set { txt = value; Console.WriteLine("Txt Setter Called!");}
}
}
Everytime I click on Button the setter of Txt is being called. Why? The value was not changed.
GetHashCode() method returns same results.
What am I missing??? :-)
Well, that's the way it's supposed to work, but the name might be a bit misleading.
The source value is being updated when the target property has been set, not necessarily when the value has changed, as the name suggests.
You can observe the same behavior by using fx. a CheckBox. Setting the IsChecked property to true over and over will also trigger a source update even though the target value does not change.
So the binding system does not compare the actual values before triggering an update, it just cares about whether the target property was set or not.
Your example extended with a CheckBox:
XAML:
<StackPanel>
<TextBox x:Name="tbx1" Text="{Binding Txt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<CheckBox x:Name="chk1" IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Change Text" Click="OnClick" />
</StackPanel>
Code-behind:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
private void OnClick(object sender, RoutedEventArgs e)
{
tbx1.Text = "hello";
chk1.IsChecked = true;
}
}
public class ViewModel
{
private string txt;
public string Txt
{
get { return txt; }
set { txt = value; Console.WriteLine("Txt Setter Called!"); }
}
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set { isChecked = value; Console.WriteLine("IsChecked Setter Called!"); }
}
}
As you expect, there is no actual "property change" happening, which can be confirmed with:
using System.ComponentModel;
var descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
descriptor.AddValueChanged(tbx1, (s, e) => Console.WriteLine("tbx1 changed"));
"tbx1 changed" will only appear once.
If the source is a dependency property, it doesn't change either. Try adding another textbox and using it as the source instead of the viewmodel:
<TextBox Name="tbx2" />
<TextBox Name="tbx1" Text="{Binding ElementName=tbx2, Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Handler:
descriptor.AddValueChanged(tbx2, (s, e) => Console.WriteLine("tbx2 changed"));
Again, only one change.
So yes, the trigger is not accurately named. A reason for this behavior may be to ensure that a property set always triggers a binding converter, because a ConvertBack could return a different value even with the same input.
In any event, both target and source need to take some responsibility in determining what a "change" is. After all, if it's a true two-way binding, then we should be allowed to implement OnClick this way with exactly the same effect:
tbx1.DataContext.Txt = "hello";
So just make sure your property setters always check for an actual change before proceeding (as dependency properties do).
Change your BindingMode to OneWay or OneWayToSource.. your problem will get solve...
<TextBox x:Name="tbx1" Text="{Binding Txt, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>
You could change you setter to check if the new value is different from the old.
public string Txt
{
get { return txt; }
set {
if (txt == value) return;
txt = value;
Console.WriteLine("Txt Setter Called!");
}
}

How can I refresh the UI in MVVM WPF

My project is based on the MVVM pattern.
I have built a tree view that shows my file system.
Each folder has a checkbox for selecting the current folder.
The selection process is taking some time so, while the operation runs, there is a button which is disabled and at the end of the operation I`m enabling the button.
My problem is that when the button gets "disabled" I see it immediately. However, when the button is going back to the enabled mode I must do some action (like mouse click) to see the button enabled.
How can I make sure that the UI will be updated immediately after the button is enabled?
These are my buttons:
<Button Content="<- Back" Margin="5,0,5,0" Width="80" Height="25"
IsEnabled="{Binding CanMoveToPreviousPage, UpdateSourceTrigger=PropertyChanged}"
Command="{Binding Path=NavigateBackCommand, IsAsync=True}" />
<Button Content="{Binding ButtonNextCaption}" Margin="5,0,5,0" Width="80" Height="25"
IsEnabled="{Binding CanMoveToNextPage, UpdateSourceTrigger=PropertyChanged}"
Command="{Binding Path=NavigateNextCommand, IsAsync=True}" />
In my ViewModel I added this code:
public bool CanMoveToNextPage
{
get
{
return this.CurrentPage != null && this.CurrentPage.CanMoveNext;
}
set
{
if (CurrentPage != null)
{
this.CurrentPage.CanMoveNext = value;
OnPropertyChanged("CanMoveToNextPage");
}
}
}
public bool CanMoveToPreviousPage
{
get { return 0 < this.CurrentPageIndex && CurrentPage.CanMoveBack; }
set
{
if (CurrentPage != null)
{
this.CurrentPage.CanMoveBack = value;
OnPropertyChanged("CanMoveToPreviousPage");
}
}
}
The UI update happens just after I execute a mouse click or any keystroke.
This is the code of the action that is disabling and enabling the buttons:
void bg_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
DecrementDoneCounter();
if (ThreadSafeCouner == 0)//means all bg workers are done
{
UIlimitation(true);
}
}
private int ThreadSafeCouner; // check how many bgworkers run
public void IncrementDoneCounter() { Interlocked.Increment(ref ThreadSafeCouner); }
public void DecrementDoneCounter() { Interlocked.Decrement(ref ThreadSafeCouner); }
void bg_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
IncrementDoneCounter();
UIlimitation(false);
((bgArguments)e.Argument).SelectedDirectory.CanSelected = false;
MarkItems(((bgArguments)e.Argument).SelectedDirectory, ((bgArguments)e.Argument).IsSelect);
((bgArguments)e.Argument).FreeWorkerAllocation();
((bgArguments)e.Argument).SelectedDirectory.CanSelected = true;
}
//this is the enabling action which execute the propeties setters at the upper part of this post
private static void UIlimitation(bool limit)
{
MainWindowViewModel.Instance.CanMoveToNextPage = limit;
MainWindowViewModel.Instance.CanMoveToPreviousPage = limit;
}
What can I do?
You can adjust on your control Binding mode TwoWay and define triggers with PropertyChanged
{Binding ElementName=.., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
OK I found a solution.
I tried everything without success and eventually I found this thread:
Refresh WPF Command
I have used CommandManager.InvalidateRequerySuggested()
And its works.
Thanks for your help
Here's a code example of how you might set up your ViewModel with the INotifyPropertyChanged method of sending messages to update the UI:
public class MyViewModel : INotifyPropertyChanged
{
/******************************************************/
/* Property that you have created two-way binding for */
/******************************************************/
private double _myProperty
public double MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
OnNotifyPropertyChanged("MyProperty");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnNotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged Members
}

ObservableCollection<T> not updating UI

I'm having an issue with an ObservableCollection getting new items but not reflecting those changes in a ListView. I have enough quirks in the way I'm implementing this that I'm having a hard time determining what the problem is.
My ObservableCollection is implemented thusly:
public class MessageList : ObservableCollection<LobbyMessage>
{
public MessageList(): base()
{
Add(new LobbyMessage() { Name = "System", Message = "Welcome!" });
}
}
I store the collection in a static property (so that its easily accessible from multiple user controls):
static public MessageList LobbyMessages { get; set; }
In the OnLoad event of my main NavigationWindow I have the following line:
ChatHelper.LobbyMessages = new MessageList();
My XAML in the UserControl where the ListView is located reads as:
<ListBox IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Mode=OneWay}"
x:Name="ListBoxChatMessages"
d:UseSampleData="True"
ItemTemplate="{DynamicResource MessageListTemplate}"
IsEnabled="True">
<ListBox.DataContext>
<Magrathea_Words_Tools:MessageList/>
</ListBox.DataContext>
</ListBox>
The initial message that I added in the constructor appears in the UI just fine.
Now, the way I add new items to the collection is from a CallBack coming from a WCF service. I had this code working in a WinForms application and it was neccessary to marshall the callback to the UI thread so I left that code in place. Here is an abbreviated version of the method:
Helper.Context = SynchronizationContext.Current;
#region IServiceMessageCallback Members
/// <summary>
/// Callback handler for when the service has a message for
/// this client
/// </summary>
/// <param name="serviceMessage"></param>
public void OnReceivedServiceMessage(ServiceMessage serviceMessage)
{
// This is being called from the WCF service on it's own thread so
// we have to marshall the call back to this thread.
SendOrPostCallback callback = delegate
{
switch (serviceMessage.MessageType)
{
case MessageType.ChatMessage:
ChatHelper.LobbyMessages.Add(
new LobbyMessage()
{
Name = serviceMessage.OriginatingPlayer.Name,
Message = serviceMessage.Message
});
break;
default:
break;
}
};
Helper.Context.Post(callback, null);
}
While debugging I can see the collection getting updated with messages from the service but the UI is not reflecting those additions.
Any ideas about what I'm missing to get the ListView to reflect those new items in the collection?
I resolved this issue.
Neither the static property or the context of the incoming data had anything to do with the issue (which seems obvious in hindsight).
The XAML which was generated from Expression Blend was not up to the task for some reason. All I did to get this to work was assign the ItemSource to the collection in C#.
ListBoxChatMessages.ItemsSource = ChatHelper.LobbyMessages.Messages;
My XAML is now more simplified.
<ListBox IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Mode=OneWay}" Background="#FF1F1F1F"
Margin="223,18.084,15.957,67.787" x:Name="ListBoxChatMessages"
ItemTemplate="{DynamicResource MessageListTemplate}"
IsEnabled="True"/>
I'm a little confused as to why this works. I was reading the MSDN articles on how to bind data in WPF and they included several binding objects, referencing properties on object, etc. I don't understand why they went to all the trouble when one line of code in the UserControl's constructor does the trick just fine.
You need to make your poco class within the ObservableCollection implement INotifyPropertyChanged.
Example:
<viewModels:LocationsViewModel x:Key="viewModel" />
.
.
.
<ListView
DataContext="{StaticResource viewModel}"
ItemsSource="{Binding Locations}"
IsItemClickEnabled="True"
ItemClick="GroupSection_ItemClick"
ContinuumNavigationTransitionInfo.ExitElementContainer="True">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="0,0,10,0" Style="{ThemeResource ListViewItemTextBlockStyle}" />
<TextBlock Text="{Binding Latitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Longitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class LocationViewModel : BaseViewModel
{
ObservableCollection<Location> _locations = new ObservableCollection<Location>();
public ObservableCollection<Location> Locations
{
get
{
return _locations;
}
set
{
if (_locations != value)
{
_locations = value;
OnNotifyPropertyChanged();
}
}
}
}
public class Location : BaseViewModel
{
int _locationId = 0;
public int LocationId
{
get
{
return _locationId;
}
set
{
if (_locationId != value)
{
_locationId = value;
OnNotifyPropertyChanged();
}
}
}
string _name = null;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnNotifyPropertyChanged();
}
}
}
float _latitude = 0;
public float Latitude
{
get
{
return _latitude;
}
set
{
if (_latitude != value)
{
_latitude = value;
OnNotifyPropertyChanged();
}
}
}
float _longitude = 0;
public float Longitude
{
get
{
return _longitude;
}
set
{
if (_longitude != value)
{
_longitude = value;
OnNotifyPropertyChanged();
}
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnNotifyPropertyChanged([CallerMemberName] string memberName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(memberName));
}
}
}

Resources