Explanation
I'm creating my own search control in WPF. This control is a UserControl that contains an area with search parameters (eg.: search on specific ID, name,...) and a GridView that shows the result.
In my control I have a dependency property of type ICommand where I bind the command to execute my search query.
public static readonly DependencyProperty SearchCommandProperty =
DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));
Use of my control in a certain window:
<customControls:SearchControl SearchCommand="{Binding SearchItemsCommand}"
SearchResult="{Binding SearchResult}" />
SearchItemsCommand is a Command in my ViewModel where I can find my search query.
In this command you can find my query to retrieve the result.
SearchResult is my ICollection that contains the result of the search query.
Code of the commands
Viewmodel
private DelegateCommand searchItemsCommand;
public DelegateCommand SearchItemsCommand
{
get
{
if (this.searchItemsCommand== null)
this.searchItemsCommand= new DelegateCommand(this.SearchItemsCommandExecuted);
return this.searchItemsCommand;
}
}
private ICollection<VoucherOverviewModel> voucherResults;
private void SearchItemsCommandExecuted()
{
using (DbContext context = new DbContext())
{
var query = (from v in context.Vouchers
join vt in context.VoucherTransactions on new
{
voucherID = v.VoucherID,
type = VoucherTransactionType.Out
} equals new
{
voucherID = vt.VoucherID,
type = vt.Type
}
join vtype in context.VoucherTypes on v.VoucherTypeID equals vtype.VoucherTypeID
join c in context.Customers on vt.CustomerID equals c.CustomerID
join pos in context.PointOfSales on v.PointOfSaleID equals pos.PointOfSaleID
select new VoucherOverviewModel()
{
PointOfSaleID = v.PointOfSaleID,
PointOfSaleName = pos.Name,
VoucherID = v.VoucherID,
VoucherCode = v.Code,
VoucherTypeID = v.VoucherTypeID,
VoucherTypeDescription = vtype.Code,
CustomerID = c.CustomerID,
CustomerName = c.Name,
Value = vt.Value,
UsedValue = context.VoucherTransactions
.Where(x => x.VoucherID == v.VoucherID &&
x.Type == VoucherTransactionType.In)
.Sum(x => x.Value),
CreateDate = vt.Date,
ValidFrom = v.ValidFrom,
ValidUntil = v.ValidUntil,
ParentVoucherID = v.ParentVoucherID,
Comment = v.Comment,
});
foreach (ISearchParameter searchParameter in this.SearchParameters)
{
if (!searchParameter.Value.IsNullOrDefault())
{
switch ((FilterVoucherParameterKey)searchParameter.Key)
{
case FilterVoucherParameterKey.CustomerID:
query = query.Where(x => x.CustomerID == (int)searchParameter.Value);
break;
case FilterVoucherParameterKey.VoucherID:
query = query.Where(x => x.VoucherCode.Contains((string)searchParameter.Value));
break;
case FilterVoucherParameterKey.PointOfSale:
query = query.Where(x => x.PointOfSaleID == (byte)searchParameter.Value);
break;
case FilterVoucherParameterKey.Type:
query = query.Where(x => x.VoucherTypeID == (byte)searchParameter.Value);
break;
}
}
}
this.voucherResults = query.ToList();
}
}
Custom control
public static readonly DependencyProperty SearchCommandProperty =
DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));
public ICommand SearchCommand
{
get
{
return (ICommand)this.GetValue(SearchCommandProperty);
}
set
{
this.SetValue(SearchCommandProperty, value);
}
}
This is my dependency property so that I can bind the SearchItemsCommand to my Custom control.
Then I have another ICommand to execute the binded command and show the loading element in my custom control.
This LocalSearchCommand will be executed when you click on a button.
private DelegateCommand localSearchCommand;
public DelegateCommand LocalSearchCommand
{
get
{
if (this.localSearchCommand == null)
this.localSearchCommand = new DelegateCommand(this.LocalSearchCommandExecuted);
return this.localSearchCommand;
}
}
private void LocalSearchCommandExecuted()
{
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
t.Dispose();
}
});
}
The problem
I want to show a Loading element when the query is executing to interact with the user. To show this element, I have to set it visible.
The problem now is, when I set it visible and want to execute the search command, my whole UI freezes. After the result is fetched from the database and generated in the GridView, then, and only then, it shows my loading element. I do understand why this happens and I tried to solve it using a Task.
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
t.Dispose();
}
});
I have to use the Dispatcher in my Task to execute the SearchCommand, because it is owned by the UI-thread.
But because of the use of the Dispatcher class, I have the same problem as before. My loading element is only shown when the query is already executed, because the Dispatcher executes the search command back on the UI-thread.
Without the use of the Dispatcher class, it gives me the following error:
The calling thread cannot access this object because a different thread owns it.
I get this error on the line:
return (ICommand)this.GetValue(SearchCommandProperty);
Even with an empty SearchItemsCommandExecuted method, the error occurs.
What I already tried
I tried setting the TaskScheduler of the Task to
TaskScheduler.FromCurrentSynchronizationContext()
I used a lot of combinations of BeginInvoke and Invoke.
I tried to set the Visibility of the loading element in the Task.
But none of the above did work.
How can I solve my problem, so that the loading element is shown when the query is executing. Did I miss something obvious?
Thanks in advance!
Loetn
The problem is that you are creating a new Task with a ThreadPool thread, but using Dispatcher.Invoke, which runs your command on the UI Thread, hence why your UI is freezing.
You need to offload your SearchCommand work to a background thread, then update your UI with a Continuation on the UI Thread (Dont try updating your UI inside SearchCommand):
then, it shows my loading element. I do understand why this happens and I tried to solve it using a Task.
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
return this.SearchCommand.Execute(null);
})
.ContinueWith(t =>
{
MyUIElement = t.Result; // Update your UI here.
}, TaskScheduler.FromCurrentSynchronizationContext());
Edit: Did not catch your binding of the first command to the second. So the following will proably not work. Looking into it...
EDIT 2: I assumed you want to start the background operation from your viewmodel. In the moment i can't think of another way than to make your loadingItem.Visible property a dependency property, move the background operation to your viewmodel, assign a property which is bound to loadingItem.Visible from there and remove the asynchronus stuff from your usercontrol.
You want to start your query on the background thread and assign the result to your ui thread:
private void LocalSearchCommandExecuted(object obj)
{
//can also be your loadingItem.
VisibleElement.Visibility = Visibility.Collapsed;
//get the ui context
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
//do your query on the background thread
LongRunningOperation();
})
//this happens on the ui thread because of the second parameter scheduler
.ContinueWith(t =>
{
if (t.IsCompleted)
{
VisibleElement.Visibility = Visibility.Visible;
//assign the result from the LongRunningOperation to your ui list
_list = new List<string>(_tempList);
//if you need to...
RaisePropertyChanged("SearchResults");
}
}, scheduler );
}
private void LongRunningOperation()
{
//assign your result to a temporary collection
//if you do not do that you will get an exception: An ItemsControl is inconsistent with its items source
_tempList = new List<string>();
for (int i = 0; i < 100; i++)
{
_tempList.Add("Test" + i);
Thread.Sleep(10);
}
}
I solved my problem with the help of this blog.
What I had to do is to edit the getter of my Dependency property SearchCommand so that it uses the Dispatcher.
public ICommand SearchCommand
{
get
{
return (ICommand)this.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Background,
(DispatcherOperationCallback)delegate { return this.GetValue(SearchCommandProperty); },
SearchCommandProperty);
// Instead of this
// return this.GetValue(SearchCommandProperty);
}
set
{
this.SetValue(SearchCommandProperty, value);
}
}
And this is my Command method:
private void LocalSearchCommandExecuted()
{
this.loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.SearchCommand.Execute(null);
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
this.Dispatcher.BeginInvoke((Action)(() => this.loadingElement.Visible= false));
t.Dispose();
}
});
}
Thanks for all the help!
Related
(1) I am having trouble getting the CollectionChanged event of an ObservableCollection to fire whilst using Akavache, here is the code I have (simplified)
GraphCollection = new ObservableCollection<UserData>();
_cache.GetOrFetchObject(TabType.Graph.ToString(),
async () => await _dataStore.GetAllDocuments(TabType.Graph))
.Subscribe(
GraphList =>
{
GraphCollection = new ObservableCollection<UserData>(GraphList);
//GraphCollection.Shuffle();
NoGraphItems = GraphCollection.Count == 0;
});
GraphCollection.CollectionChanged += (sender, args) =>
{
NoGraphItems = GraphCollection.Count == 0;
};
Ideally, when I Add/Delete data I want to trigger the event to check to see if the collection is empty and then assign a bool property that it is or isn't empty.
I am making simple Add/Delete like so, and then calling a RefreshCache method to invalidate the data and recreate the data, not sure if this is the most efficient way to do it as well.
var graphRecord = GraphCollection.FirstOrDefault(x => x.Id == data.Id);
GraphCollection.Remove(dreamRecord);
RefreshCache(TabType.Graphs, DreamsCollection);
private void RefreshCache(TabType tabType, ObservableCollection<UserData> collection)
{
_cache.InvalidateObject<UserData>(tabType.ToString());
_cache.InsertObject(tabType.ToString(), collection);
}
(2) I am not currently setting the DateTime offset, do I need this? Can someone give me an example of how to write it out, the docs don't clearly state this.
DateTimeOffset? absoluteExpiration
your Subscribe creates a new instance of GraphCollection so the event handler that was assigned to the original instance no longer works with the new instance
try this instead
GraphList =>
{
GraphCollection = new ObservableCollection<UserData>(GraphList);
NoGraphItems = GraphCollection.Count == 0;
GraphCollection.CollectionChanged += // handler goes here
});
This question already has an answer here:
Lifeupdate DataGrid from TextFile with good performance
(1 answer)
Closed 5 years ago.
I have a background thread to listen data, so this infinite loop to output data.
And, I using a ObservableCollection to bind a ListBox and show log.
I try to use Dispather.BeginInvoke, but no use, it not realtime. I have no idea for update UI in MVVM.
xaml
<ListBox x:Name="lsb_log" Width="auto" ItemsSource="{Binding DisplayLogs}"/>
code
private ObservableCollection<string> _displayLogs;
public ObservableCollection<string> DisplayLogs
{
get
{
return _displayLogs;
}
}
public Testpro()
{
_displayLogs = new ObservableCollection<string>();
Task.Run(new Action(outputData));
}
private void outputData()
{
string str = "";
while (true)
{
string newString = GetInfoString();
if (string.IsNullOrWhiteSpace(newString) || str == newString)
continue;
str = newString;
Debug.WriteLine(str);
Application.Current.Dispatcher.BeginInvoke((Action)delegate ()
{
DisplayLogs.Add(str);
});
}
}
*Edit:
GetInfoString will call by a dll file.
You should really use something designed for this kind of update. I'm going to suggest Microsoft's Reactive Framework (NuGet "System.Reactive" & "System.Reacive.Windows.Threading").
Then you can do this after the _displayLogs = new ObservableCollection<string>(); line:
IDisposable subscription =
Observable
.Interval(TimeSpan.FromSeconds(1.0))
.Select(_ => GetInfoString())
.Where(newString => !string.IsNullOrWhiteSpace(newString))
.DistinctUntilChanged()
.ObserveOnDispatcher()
.Subscribe(newString =>
{
Debug.WriteLine(newString);
DisplayLogs.Add(newString);
});
That should call GetInfoString() every second and update your UI. It handles all of the threading and marshalling issues.
If you keep a reference to subscription you can stop it at any stage by calling subscription.Dispose().
You can use async-await therefore. Your OutputData-Method can look something like:
private async void OutputData(IProgress<string> onProgress)
{
await Task.Factory.StartNew(() =>
{
string str = string.Empty;
while (true)
{
string newString = GetInfoString();
if(string.IsNullOrWhiteSpace(newString) || str == newString)
continue;
str = newString;
// call the UI to update your bound collection
onProgress.Report(str);
// give the ui some time to respond before continue with your endless-loop
Thread.Sleep(200);
}
});
}
And you can call this method with:
OutputData(new Progress<string>((str) =>
{
DisplayLogs.Add(str);
}));
That's it
I have an error accessing a WPF element in code behind (see indicated error line):
Thumb sThumb = new Thumb();
...
TextBox Thumbtxt = (TextBox)sThumb.Template.FindName("TextThumb", sThumb);
if (Thumbtxt.Tag != null) <-- Unhandled Exception: The calling thread cannot access this object because a different thread owns it.
{
if (Thumbtxt.Tag.ToString() == "Disabled")
IsDisabled = true;
}
I then tried to execute the code in the dispatcher, as follows:
System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
{
Thumb sThumb = new Thumb();
...
TextBox Thumbtxt = (TextBox)sThumb.Template.FindName("TextThumb", sThumb);
if (Thumbtxt.Tag != null)
{
if (Thumbtxt.Tag.ToString() == "Disabled")
IsDisabled = true;
}));
Still, I get the same error in the very same line. I use this dispatcher invoke in other parts and it works fine, but apparently it doesn't like the "Tag" property, for some reason.
Can someone please shed some light?
Thanks in advance
Try to use TaskScheduler.FromCurrentSynchronizationContext
await Task.Run(() => { TextBox Thumbtxt = (TextBox)sThumb.Template.FindName("TextThumb", sThumb); })
.ContinueWith(
res =>
{
you code here
},
TaskScheduler.FromCurrentSynchronizationContext());
Try to use the dispatcher that is associated with the Thumbtxt control:
Thumbtxt.Dispatcher.Invoke(new Action(()=>
{
if (Thumbtxt.Tag != null)
{
if (Thumbtxt.Tag.ToString() == "Disabled")
IsDisabled = true;
};
}));
I'm quite new to threads in c# (WPF) and since I've implemented some label and progressbar update successfully, I do not understand Why when I try to add items to the treeView of my GUI from another class called in a separate thread I get an exception:
An unhandled exception of type 'System.InvalidOperationException'
occurred in WindowsBase.dll
Additional information: The calling thread cannot access this object
because a different thread owns it.
My update treeview code is this:
private void updateTreeView(TreeView tree, List<TreeViewItem> items, Boolean clear) {
tree.Dispatcher.Invoke(new Action(() => {
if (clear) {
tree.Items.Clear();
}
ItemCollection treeitems = tree.Items;
foreach (TreeViewItem item in items) {
treeitems.Dispatcher.Invoke(new Action(() => {
treeitems.Add(item);
}));
}
tree.ItemsSource = treeitems;
}));
}
And the exception points at the line:
treeitems.Add(item);
Thanks in advance.
you can use the following :
delegate void DUpdateTreeView(TreeView tree, List<TreeViewItem> items, Boolean clear);
private void UpdataTreeView(TreeView tree, List<TreeViewItem> items, Boolean clear)
{
if (tree.InvokeRequired)
{
DUpdateTreeView d = new DUpdateTreeView(UpdataTreeView);
// replace this by the main form object if the function doesn't in the main form class
this.Invoke(d, new object[] { tree, items, clear });
}
else
{
if (clear)
{
tree.Items.Clear();
}
else
{
// Here you can add the items to the treeView
/***
ItemCollection treeitems = tree.Items;
foreach (TreeViewItem item in items)
{
treeitems.Dispatcher.Invoke(new Action(() =>
{
treeitems.Add(item);
}));
}
tree.ItemsSource = treeitems;
***/
}
}
}
This is a really old question but I figured I would answer it. You have two dispatchers in your sample. You have a treeview that you are getting its thread and a list that seems to be created in a different thread.
But the code should look more like this. Sorry about the VB in this case I'm using a delegate inside the invoke.
tree.Dispatcher.BeginInvoke(Sub()
Dim node = new TreeViewItem() With {.Header = "Header"}
tree.items.add(node)
End Sub)
I am not jumping out of the UI thread to add the node like in the original question.
Is there an equivalent to Form.InvokeRequired in WPF, e.g. Dispatcher.InvokeRequired?
This is slightly odd as it doesn't appear in intellisense, but you can use:
var dispatcher = myDispatcherObject.Dispatcher;
if (dispatcher.CheckAccess()) { /* ... */ }
As all UI components inherit from DispatcherObject this should solve your specific problem, but it is not specific to the UI thread - it can be used for any dispatcher.
The equivalent is Dispatcher.CheckAccess.
A possible solution that came to mind is:
if ( Dispatcher.Thread.Equals( Thread.CurrentThread ) )
{
Action( );
}
else
{
Dispatcher.Invoke( Action );
}
If you're building a Windows Store app, the above example won't work. Here's an example that does work. Modify as needed, of course :)
/// <summary>
/// Updates the UI after the albums have been retrieved. This prevents the annoying delay when receiving the albums list.
/// </summary>
/// <param name="albums"></param>
public void UpdateUiAfterAlbumsRetrieved(System.Collections.ObjectModel.ObservableCollection<PhotoAlbum> albums)
{
if (!Dispatcher.HasThreadAccess)
{
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
ddlAlbums.DataContext = albums;
ddlAlbums.IsEnabled = true;
tbxProgress.Text = String.Empty;
ProgressBar.IsIndeterminate = false;
ProgressBar.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
});
}
else
{
ddlAlbums.DataContext = albums;
ddlAlbums.IsEnabled = true;
tbxProgress.Text = String.Empty;
ProgressBar.IsIndeterminate = false;
}
}