Dispatcher Invoke multi control in one delegate? - wpf

I am working on a WPF application,
I have to fetch the picasa album from google API and bind to WPF listview.
I have to bind to 2 listview and using Dispatcher.Invoke().
Below is the code snippet:
private void BindPicasa()
{
//My custom Google helper class.
GoogleClass google = new GoogleClass();
ThreadStart start = delegate()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(delegate()
{
//Fetch the album list
List<AlbumClass.Album> album = google.RequestAlbum(GoogleID);
//bind to a 1st listview in text title.
ListLeftAlbum.DataContext = album;
//bind to a 2nd listview in thumbnail preview.
ListMainAlbum.Visibility = System.Windows.Visibility.Visible;
ListMainAlbum.DataContext = album;
}));
};
new Thread(start).Start();
}
The UI will freeze when start,
However if i take out each listview and run with its own Dispatcher, it is fine, the UI responsible, but it seems not elegant way to do so.
Any recommend? Thanks you!
private void BindPicasa()
{
//My custom Google helper class.
GoogleClass google = new GoogleClass();
ThreadStart start = delegate()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(delegate()
{
//Fetch the album list
List<AlbumClass.Album> album = google.RequestAlbum(GoogleID);
}));
ListLeftAlbum.Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(delegate()
{
//bind to a 1st listview in text title.
ListLeftAlbum.DataContext = album;
}));
ListMainAlbum.Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(delegate()
{
//bind to a 2nd listview in thumbnail preview.
ListMainAlbum.Visibility = System.Windows.Visibility.Visible;
ListMainAlbum.DataContext = album;
}));
};
new Thread(start).Start();
}

your first version was almost right, you just need to fetch the album on the background thread instead of dispatching the request back to the ui thread
private void BindPicasa()
{
//My custom Google helper class.
GoogleClass google = new GoogleClass();
ThreadStart start = delegate()
{
//Fetch the album list
List<AlbumClass.Album> album = google.RequestAlbum(GoogleID);
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(delegate()
{
//bind to a 1st listview in text title.
ListLeftAlbum.DataContext = album;
//bind to a 2nd listview in thumbnail preview.
ListMainAlbum.Visibility = System.Windows.Visibility.Visible;
ListMainAlbum.DataContext = album;
}));
};
new Thread(start).Start();
}

Related

MVVM Light Messenger.Register not working

In my MainViewModel I have only RelayCommands that open different pages.
Theese commands are like this
Messenger.Default.Send<int>(2015);
ViewModel.ReportViewModel reportVM = new ReportViewModel(report);
Views.ReportView pagReport = new ReportView() { DataContext = reportVM };
ApplicationHelper.NavigationService(pagReport);
in ReportViewModel I have
public ReportViewModel(string report)
{
Messenger.Default.Register<int>(this, Doit);
ShowReport(report);
}
private void Doit(int val)
{
int test = val;//code never touch this line
}
What I'm doing wrong ?
Per my comment above, you're instantiating/newing up reportVM after you send the message. If there isn't an instance of ReportViewModel before the message is sent then that message has no listener.
//Instantiate first:
ViewModel.ReportViewModel reportVM = new ReportViewModel(report);
Views.ReportView pagReport = new ReportView() { DataContext = reportVM };
ApplicationHelper.NavigationService(pagReport);
//Send the message:
Messenger.Default.Send<int>(2015);

WPF InvalidOperationException Getting MainWindow Reference

I have MainWindow, which contains an instance of DemoUI (a UserControl).
From within a class instance called DemoModule, I have a reference to DemoUI which I call _demoUI.
When I try to get a reference to the MainWindow from within DemoModule using
var parentWindow = Window.GetWindow(_demoUI);
I get this InvalidOperationException:
The calling thread cannot access this object because a different thread owns it.
Ultimately, I want to be able to update the MainWindow's progress bar's value using it's Dispatcher as follows:
var progressBar = parentWindow.FindName("ProgressBar") as ProgressBar;
progressBar.Dispatcher.Invoke(DispatcherPriority.Normal,
new DispatcherOperationCallback(o => {
progressBar.Value = Progress = args.Current;
return null;
}), null);
Update 1
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
Progress = Convert.ToInt32(args.Current * 100);
var progressBar = Application.Current.MainWindow.FindName("ProgressBar") as ProgressBar;
if (progressBar != null)
progressBar.Value = Progress;
}
From the code you include here it doesn't seem like you need to get the progressBar reference in order to update the ProgressBar on your _demoUI? Is there another ProgressBar on the MainWindow? Regardless, you can use the _demoUI reference to get to the dispatcher.
_demoUI.Dispatcher.Invoke(DispatcherPriority.Normal,
new DispatcherOperationCallback(o => {
_demoUI.ProgressBar.Value = Progress = args.Current;
return null;
}), null);
Or
_demoUI.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(o => {
var window = Window.GetWindow(_demoUI);
//do what you need to with the window here.
}), null);

asynchronous UI update from ViewModel in WPF

I am having a problem with getting data from db and showing in UI asynchronously.
I am using MVVM light, when I click the button, action is triggered in ViewModel:
private void SearchQuery(string query)
{
_redisModel.GetFriendsListAsync(query);
}
At some point GetFriendsListCompleted is called by background thread notifing viewmodel that job is done.
At this point I need to update ListBox ItemSource. But when I try to update is I get
“The calling thread cannot access this object because a different thread owns it”
I have tried Dispatcher.CurrentDispatcher.Invoke(),App.Current.Dispatcher.Invoke() and different magic, but it still doesn’t work.
I tried to give UI dispatcher to ViewModel and then call it from there - didn't work.
private string filterText = string.Empty;
public string FilterText
{
get { return filterText; }
set
{
filterText = value;
this.RaisePropertyChanged(() => this.FilterText);
this.FriendsList.View.Refresh(); // Here where exception is happening.
}
}
I tried to change this line to
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() =>this.FriendsList.View.Refresh())); - still the same.
I am using Telerik ListBox to display items. FriendList is CollectionViewSource(http://www.telerik.com/help/wpf/radlistbox-overview.html). It works when I use Telerik example from WPF Control Examples. Problems start to occur when I use my async methods.
Type of view is System.ComponentModel.ICollectionView it is used for Filtering and Grouping.
I have also tried to just assign ObservableCollection to Items property of the ListBox and it doesn't work either.
A bit more details on how _redisModel.GetFriendsListAsync works:
In the end(after all chain of calls) it ends up here:
public GetAsyncResult(Func<T> workToBeDone, Action<IAsyncResult> cbMethod, Object state)
{
_cbMethod = cbMethod;
_state = state;
QueueWorkOnThreadPool(workToBeDone);
}
ThreadPool.QueueUserWorkItem(state =>
{
try
{
_result = workToBeDone();
}
catch (Exception ex)
{
_exception = ex;
}
finally
{
UpdateStatusToComplete(); //1 and 2
NotifyCallbackWhenAvailable(); //3 callback invocation
}
});
In viewmodel I have method:
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => this.FriendsList.View.Refresh()));
}
}
Can anybody please help me with this ?
Thank you
You are creating CollectionViewSource in one thread and refreshing that in another thread (dispatcher thread). Update your GetFriendsListCompleted to
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => {
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
this.FriendsList.View.Refresh();
}));
}
}
}
You haven't shown any of the code that's actually running on the background thread on completion but I'm guessing that in it you're creating a collection object that you're then trying to assign to your CollectionView. When the CV tries to update (on the UI thread) from your Refresh call it would then try to use the collection that's owned by the other thread.
If you include the relevant code it would be easier to say for sure.

Print WPF grid is cropped

I found function to print content of Wpf grid but the print is cropped.
Can anybody know why?
the function:
private void PrintTest()
{
FlowDocument document;
Window window;
CreateWindowToPrint(out document, out window);
PrintDialog printDialog = new PrintDialog();
window.Show();
IDocumentPaginatorSource dps = document;
if (printDialog.ShowDialog() == true)
{
printDialog.PrintDocument(dps.DocumentPaginator, "test");
}
}
and:
private void CreateWindowToPrint(out FlowDocument document, out
Window window)
{
document = new FlowDocument { };
var test = new PrintedTest() { DataContext = this.DataContext };
document.Blocks.Add(new BlockUIContainer { Child = test });
window = new Window {Content = document, Visibility = System.Windows.Visibility.Hidden };
}
The UserControl PrintedTest contains my grid.
I don't really have much experience with printing in WPF but I thought I would give it a try.
I could reproduce your problem, and I could not solve it so far.
But In my research I have found an alternative, which is more simple to print the Grid:
var printDialog = new PrintDialog();
var result = printDialog.ShowDialog();
if (result.HasValue && result.Value)
{
var testControl = new PrintedTest() { DataContext = this.DataContext };
printDialog.PrintVisual(testControl, "My WPF printing a DataGrid");
}
Instead of sending the PrintTest you could actually just send directly the grid.

How do I create a Treeview with a Context Menu in Silverlight 4?

Silverlight 4 now include the option for creating a context menu upon right clicking. Can anyone provide me with an example of a treeview with a right click context menu for the treeview?
Ultimately I want a the menu to show different options depending upon the node depth selected - bonus points if the example includes this!
You can use this open source menu for this:
http://sl4popupmenu.codeplex.com
The control supports right click on TreeViews out of the box. The code has been adapted from the sample code on the homepage to use a TreeView instead of a DataGrid:
private void GenerateMenu()
{
var data = new ObservableCollection<string>("Item 1,Item 2,Item 3,Item 4,Item 6,Item 7,Item 8".Split(','));
TreeView treeView1 = new TreeView() { Margin = new Thickness(50), ItemsSource = data };
this.LayoutRoot.Children.Add(dataGrid1);
// Create the submenu
var pmTimeSub = new PopupMenu();
pmTimeSub.AddItem("Time Now", null);
// Create the main menu
var pm = new PopupMenu();
pm.AddItem("Delete row", delegate { data.RemoveAt(dataGrid1.SelectedIndex); });
pm.AddSeparator();
pm.AddSubMenu(pmTimeSub, "Get Time ", "images/arrow.png", null, null, false, null);
// Attach the submenu pmTimeSub
pm.AddSeparator();
pm.AddItem("Demo2", delegate { this.Content = new Demo2(); });
// Set dataGrid1 as the trigger element
pm.AddTrigger(TriggerTypes.RightClick, treeView1);
// Showing main menu
pm.Showing += (sender, e) =>
{
pm.PopupMenuItem(0).Header = "Delete " + treeView1.SelectedItem;
TreeViewItem tvi = pm.GetClickedElement<TreeViewItem>();
// Add code to calculate the node depth here using the GetParentTreeViewItem method
// Add code to modify the menu items according to the node depth value.
pm.PopupMenuItem(0).IsVisible =
pm.PopupMenuItem(1).IsVisible = tvi != null;
};
// Showing submenu
pmTimeSub.Showing += delegate
{
pmTimeSub.PopupMenuItem(0).Header = DateTime.Now.ToLongTimeString();
};
}
Note that the code does not allow you to show different menus upon the node depth yet. To do this you can use the following method to get the parent of the TreeViewItem that was clicked:
private static TreeViewItem GetParentTreeViewItem(DependencyObject item)
{
if (item != null)
{
DependencyObject parent = VisualTreeHelper.GetParent(item);
TreeViewItem parentTreeViewItem = parent as TreeViewItem;
return parentTreeViewItem ?? GetParentTreeViewItem(parent);
}
return null;
}
From there you can determine depth of the node by calling the GetParentTreeViewItem function in a loop until the parent is null. You would place this code in the event where the menu is being shown and then add the necessary code in there to show the appropriate menu.
Hope this helps.
So, I tried the above code, downloaded and attempted to include within my Existing Silverlight Application. I was able to find an easier solution. This will add a Context Menu allowing Right-Clicks on the Branches (Headers, or Parent Nodes).
private ContextMenu menu;
foreach(var model in models)
{
// Populate the Tree View Control
var cb = new CheckBox {Content = model.Value};
cb.Click += new RoutedEventHandler(cb_Click);
var header = new TreeViewItem {Header = cb};
// Menu for Header
menu = new ContextMenu();
MenuItem setAsRows = new MenuItem();
setAsRows.Header = "Set as Rows";
setAsRows.Click += new RoutedEventHandler(setAsRows_Click);
menu.Items.Add(setAsRows);
MenuItem addToRows = new MenuItem();
addToRows.Header = "Add to Rows";
addToRows.Click += new RoutedEventHandler(addToRows_Click);
menu.Items.Add(addToRows);
MenuItem setAsCols = new MenuItem();
setAsCols.Header = "Set as Columns";
menu.Items.Add(setAsCols);
MenuItem addToCols = new MenuItem();
addToCols.Header = "Add to Columns";
menu.Items.Add(addToCols);
header.ContextMenu = menu;
treeView1.Items.Add(header);
var thisItem = treeView1.Items;
// Model Contexts
var contexts = myFramework.GetConceptsOfModel(model.Key);
// Add Leafs To Branch
foreach(var context in contexts)
{
cb = new CheckBox {Content = context.Value.ToString()};
header.Items.Add(cb);
}
}

Resources