Wpf ItemSource Doesn't Update Visual Control in custom control - c#

I have a custom control with itemsSource binding:
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
Results.Clear();
foreach (var check in newValue)
{
Results.Add(check as Check);
}
}
protected ObservableCollection<Check> results = new ObservableCollection<Check>();
public ObservableCollection<Check> Results
{
get { return results; }
set { results = value; }
}
Implemented in the main view:
<control:ResultCtrl x:Name="resultCtrl" ItemsSource="{Binding Path=Results, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"></control:ResultCtrl>
Check class:
public class Check : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected string checkStateString;
public string CheckStateString
{
get { return checkStateString; }
set
{
if (value != checkStateString)
{
checkStateString = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("CheckStateString"));
}
}
}
I call a class who calculate checks in the Main View show method:
Thread t = new Thread(new ThreadStart(
delegate
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action<ResultCtrl>(AddIn.Services.Results.Comprobaciones), resultCtrl);
}
));
t.Start();
In AddIn.Services.Results.Comprobaciones I do:
resultCtrl.ItemsSource = new ObservableCollection<Check>(AddIn.Document.Results);
for every check. Every time I do that I see how ItemsSource change, but Visual only update when the AddIn.Services.Results.Comprobaciones end. I tried to do UpdateLayout() and Items.Refresh() but nothing work.
Any ideas?

This code:
Thread t = new Thread(new ThreadStart(/* ... */);
t.Start();
creates a thread, that is completely useless, because everything it does is a blocking call to UI thread's Dispatcher. In other words, 99.999...% of time it runs on UI thread. You could easily write this:
AddIn.Services.Results.Comprobaciones();
with the same result.
You have to rewrite your code for having any benefits from multi-threading. I have no idea, how does your Comprobaciones method look like, but, obviously, you should call Dispatcher.Invoke only when you need to update something in UI.
Also note, that in most cases you shouldn't create Thread instances directly. Consider using TPL instead (possibly, via async/await, if you're targeting .NET 4.5).

Related

What is missing in this update UI via dispatcher/databinding

I have a simple WPF window with: Loaded="StartTest"
and
<Grid>
<ListBox ItemsSource="{Binding Logging, IsAsync=True}"></ListBox>
</Grid>
In code behind I have in method StartTest:
LogModel LogModel = new LogModel();
void StartTest(object sender, RoutedEventArgs e)
{
DataContext = LogModel;
for (int i = 1; i<= 10; i++)
{
LogModel.Add("Test");
Thread.Sleep(100);
}
}
And class LogModel is:
public class LogModel : INotifyPropertyChanged
{
public LogModel()
{
Dispatcher = Dispatcher.CurrentDispatcher;
Logging = new ObservableCollection<string>();
}
Dispatcher Dispatcher;
public ObservableCollection<string> Logging { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void Add(string text)
{
Dispatcher.BeginInvoke((Action)delegate ()
{
Logging.Add(text);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Logging"));
});
}
}
Of course the problem is that the UI doesn't update in the loop.
What am I missing?
How can I achieve the UI update?
ObservableCollection already raises the PropertyChanged event when it's modified. You don't have to raise the event in the UI thread either.
Your model can be as simple as :
class LogModel
{
public ObservableCollection<string> Logging { get; } = new ObservableCollection<string>();
public void Add(string text)
{
Logging.Add(text);
}
}
All you need to do is set it as the DataContext of your view, eg :
LogModel model = new LogModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = model;
}
I assume StartTest is a click handler which means it runs on the UI thread. That means it will block the UI thread until the loop finishes. Once the loop finishes the UI will be updated.
If you want the UI to remain responsive during the loop, use Task.Delay instead of Thread.Slepp, eg :
private async void Button_Click(object sender, RoutedEventArgs e)
{
for(int i=0;i<10;i++)
{
await Task.Delay(100);
model.Add("Blah!");
}
}
Update
You don't need to use an ObservableCollection as a data binding source. You could use any object, including an array or List. In this case though you'd have to raise the PropertyChanged event in code :
class LogModel:INotifyPropertyChanged
{
public List<string> Logging { get; } = new List<string>();
public event PropertyChangedEventHandler PropertyChanged;
public void Add(string text)
{
Logging.Add(text);
PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Logging"));
}
}
This will tell the view to load all the contents and display them again. This is perfectly fine when you only want to display data loaded eg from the database without modifying them, as it makes mapping entities to ViewModels a lot easier. In this case you only need to update the view when a new ViewModel is attached as a result of a command.
This is not efficient when you need to update the coolection though. ObservableCollection implements the INotifyCollectionChanged interface that raises an event for each change. If you add a new item, only that item will be rendered.
On the other hand you should avoid modifying the collection in tight loops because it will raise multiple events. If you load 50 new items, don't call Add 50 times in a loop. Create a new ObservableCollection, replace the old one and raise the PropertyChanged event, eg :
class LogModel:INotifyPropertyChanged
{
public ObservableCollection<string> Logging { get; set; } = new ObservableCollection<string>();
public event PropertyChangedEventHandler PropertyChanged;
public void Add(string text)
{
Logging.Add(text);
PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Logging"));
}
public void BulkLoad(string[] texts)
{
Logging = new ObservableCollection<string>(texts);
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Logging"));
}
}
The explicit implementation is still needed because the Logging property is getting replaced and can't raise any events itself
The reason why the UI is not updated in the loop is a call to Dispatcher.BeginInvoke. This places a new DispatcherOperation in the dispatcher queue. But your loop is already a dispatcher operation, and it continues on the Dispatcher's thread. So all the operations you queue will be executed after the loop's operation is finished.
Maybe you wanted to run the StartTest on a background thread? Then, the UI will update.
By the way, don't block the Dispatcher's thread with Thread.Sleep. It prevents the Dispatcher from doing its things as smoothly as possible.
It is the DoEvents thing, overhere:
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
}
or even the perhaps better https://stackoverflow.com/a/11899439/138078.
Of course the test should be written differently in a way which does not require it.

MVVM: Update view's control visibility after completion of threadpool worker item in WPF NET 3.5

I have a WPF MVVM app under NET 3.5 and Visual Studio 2008. Some controls in the view are bound to properties on the view model, so when these properties changes it will be notified to the view through the implementation of INotifyPropertyChanged.
I have some kind of splash saying "Loading..." that appears in the center of the window at the beginning, and it keeps visible while some data is being requested from database. Once data is requested from database, I want to hide this splash.
This splash is bound to a propert, "IsSplashVisible" in view model so updating the property to true, notify the splash to be shown at the beginnig and setting it to false, notify the splash to be hidden.
Setting property "IsSplashVisible" to true at the beginning there is no problem, the problem appears when setting the property to false once queued work item finishes. Once set this property to false, control (splash "Loading...") is notified and it tries to hide but fails as this is a different thread that the one who created it so the typical exception is thrown. So how can I solve this?
Below the code.
View model:
public class TestViewModel : BaseViewModel
{
private static Dispatcher _dispatcher;
public ObservableCollection<UserData> lstUsers
public ObservableCollection<UserData> LstUsers
{
get
{
return this.lstUsers;
}
private set
{
this.lstUsers= value;
OnPropertyChanged("LstUsers");
}
}
private bool isSplashVisible = false;
public bool IsSplashVisible
{
get
{
return this.isSplashVisible;
}
set
{
if (this.isSplashVisible== value)
{
return;
}
this.isSplashVisible= value;
OnPropertyChanged("IsSplashVisible");
}
}
public TestViewModel()
{
this.IsSplashVisible = true;
ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
{
var result = getDataFromDatabase();
UIThread(() =>
{
LstUsers = result;
this.IsSplashVisible = false; <---- HERE IT FAILS
});
}));
}
ObservableCollection<UserData> getDataFromDatabase()
{
return this.RequestDataToDatabase();
}
static void UIThread(Action a)
{
if(_dispatcher == null) _dispatcher = Dispatcher.CurrentDispatcher;
//this is to make sure that the event is raised on the correct Thread
_dispatcher.Invoke(a); <---- HERE EXCEPTION IS THROWN
}
}
Dispatcher.CurrentDispatcher is not the Dispatcher of the UI thread, because it
Gets the Dispatcher for the thread currently executing and creates a new Dispatcher if one is not already associated with the thread.
You should use the Dispatcher of the current Application instance:
ThreadPool.QueueUserWorkItem(o =>
{
var result = getDataFromDatabase();
Application.Current.Dispatcher.Invoke(() =>
{
LstUsers = result;
IsSplashVisible = false;
});
});
Assuming that your TestViewModel constructor is called in the UI thread, you could have written it like shown below, where Dispatcher.CurrentDispatcher is called in the UI thread instead of a ThreadPool thread. However, the field is entirely redundant. You could always just call Application.Current.Dispatcher.Invoke().
public class TestViewModel
{
private readonly Dispatcher _dispatcher = Dispatcher.CurrentDispatcher;
public TestViewModel()
{
IsSplashVisible = true;
ThreadPool.QueueUserWorkItem(o =>
{
var result = getDataFromDatabase();
_dispatcher.Invoke(() =>
{
LstUsers = result;
IsSplashVisible = false;
});
});
}
...
}

ISupportIncrementalLoading Collection - notify UI when LoadingMoreItems is in progress

I've have created a IncrementalLoadingCollection class in my app which implements ISupportIncrementalLoading and inherits from ObservableCollection< T >.
It works fine and the items are loaded but I would like to show a message on the app's Status Bar that there is some work in progress.
What's a good way of achieving this?
Since LoadMoreItemsAsync is called internally when the list is scrolled, I cannot access that part to come up with the code which updates the Status Bar. Right now, I am doing this in LoadMoreItemsAsync which I find it a terrible approach, but I couldn't find a better one so far...
Any suggestions are highly appreciated.
Well you can for example: inherit from ObservableCollection and implement ISupportIncrementalLoading like this:
class IncrementalLoadingObservableCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading
{
private readonly Func<CancellationToken, Task<IEnumerable<T>>> _provideMoreItems;
public IncrementalLoadingObservableCollection(Func<CancellationToken, Task<IEnumerable<T>> provideMoreItems)
{
_provideMoreItems = provideMoreItems;
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
return AsyncInfo.Run(async cancelToken =>
{
await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
OnLoadMoreItemsStarted();
});
var providedItems = await _provideMoreItems(cancelToken);
await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach(var item in providedItems)
Add(item);
OnLoadMoreItemsCompleted();
});
return new LoadMoreItemsResult {Count = (uint) providedItems.Count()};;
});
}
public bool HasMoreItems
{
get { return true; }
}
public event Action LoadMoreItemsStarted;
public event Action LoadMoreItemsCompleted;
protected virtual void OnLoadMoreItemsStarted()
{
var handler = LoadMoreItemsStarted;
if (handler != null) handler();
}
protected virtual void OnLoadMoreItemsCompleted()
{
var handler = LoadMoreItemsCompleted;
if (handler != null) handler();
}
}
How to use it? In your ViewModel:
class MyFancyItemsViewModel
{
public MyFancyItemsViewModel()
{
var incrementalObservablCollcetion = new IncrementalLoading...(GetItemsFromInternetOrSmth);
incrementalObservablCollcetion.LoadMoreItemsStarted += OnItemsLoadingStarted;
incrementalObservablCollcetion.LoadMoreItemsCompleted += OnItemsLoadingCompleted;
ItemsBindedInXaml = incrementalObservablCollcetion;
}
private Task<IEnumerable<Items>> GetItemsFromInternetOrSmth(CancellationToken cancelToken)
{
... do some work returns enumerable of Items
}
private void OnItemsLoadingStarted()
{ .. do smth .. }
private void OnItemsLoadingCompleted()
{ ... do smth .. }
public ObservableCollection<Items> ItemsBindedInXaml { get; private set; }
}
You might ask why I have used Dispatcher.RunAsync in IncrementalLoadingObservableCollection - the reason is that LoadMoreItemsAsync might run on another thread (don't know that) so you have to dispatch all the work to the UI Thread (it's not possible to call UI-related methods from thread other than UI thread without use of Dispatcher).
If you feel that ViewModel is not appropiate place for UI-related operations take a look at some messaging mechanisms (like MVVM Light Messenger, register message in code-behind and send this message in LoadMoreItemsStarted handler)

Task updating properties on Main thread

I am trying to update property on my main Thread, which is bind to ProgressBar. In viewmodel I have the bellow code, which is not working.
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task task = Task.Factory.StartNew(() =>
{
DoLongRunningWork();
}).ContinueWith(_=>
{
ApplicationStatus = "Task finished!";
}, TaskScheduler.FromCurrentSynchronizationContext());
DoLongRunningWork()
{
// Alot of stuff
Task.Factory.StartNew(() =>
{
ProgressBarValue += progressTick;
}).Start(uiScheduler);
}
If the property ProgressBarValue is bound to a WPF element, then the only thread that can update the ProgressBar is the very thread that created it.
So, my assumption is that the class that contains ProgressBarValue also implements INotifyPropertyChanged. This means that you have some logic that raises the event PropertyChanged.
I would create a method that raises the event, and always does so using the Dispatcher. (The Dispatcher allows you to call functions on the thread that created your WPF controls.)
private void raisePropertyChanged(string name)
{
Dispatcher.InvokeAsync(()=>
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
});
}
This will always update the ProgressBar on the proper thread.

How can I update a command's "CanExecute" value from a different thread?

In my application, I have a Command that I only want the user to be able to trigger if it is not already running. The command in question is bound to a WPF button which means that it automagically disables the button if CanExecute is false. So far so good.
Unfortunately, the operation performed by the command is a long running one, so it needs to occur on a different thread. I didn't think that would be a problem... but it seems it is.
I have extracted out a minimal sample that will show the problem. If bound to a button (via the LocalCommands.Problem static reference) the button will be disabled as desired. When the worker thread tries to update CanExecute, an InvalidOperationException will be thrown from inside System.Windows.Controls.Primitives.ButtonBase.
What is the most appropriate way to resolve this?
Sample command code below:
using System;
using System.Threading;
using System.Windows.Input;
namespace InvalidOperationDemo
{
static class LocalCommands
{
public static ProblemCommand Problem = new ProblemCommand();
}
class ProblemCommand : ICommand
{
private bool currentlyRunning = false;
private AutoResetEvent synchronize = new AutoResetEvent(false);
public bool CanExecute(object parameter)
{
return !CurrentlyRunning;
}
public void Execute(object parameter)
{
CurrentlyRunning = true;
ThreadPool.QueueUserWorkItem(ShowProblem);
}
private void ShowProblem(object state)
{
// Do some work here. When we're done, set CurrentlyRunning back to false.
// To simulate the problem, wait on the never-set synchronization object.
synchronize.WaitOne(500);
CurrentlyRunning = false;
}
public bool CurrentlyRunning
{
get { return currentlyRunning; }
private set
{
if (currentlyRunning == value) return;
currentlyRunning = value;
var onCanExecuteChanged = CanExecuteChanged;
if (onCanExecuteChanged != null)
{
try
{
onCanExecuteChanged(this, EventArgs.Empty);
}
catch (Exception e)
{
System.Windows.MessageBox.Show(e.Message, "Exception in event handling.");
}
}
}
}
public event EventHandler CanExecuteChanged;
}
}
Change:
onCanExecuteChanged(this, EventArgs.Empty);
to:
Application.Current.Dispatcher.BeginInvoke((Action)(onCanExecuteChanged(this, EventArgs.Empty)));
Edit:
The reason for this is that WPF is listening to these events and attempting to perform actions in UI elements (I.E toggling IsEnabled in a Button), therefore these events must be raised in the UI Thread.

Categories