I have an MVVM implementation in which I have a WPF ListBox that will contain a collection of child WPF Image controls. The Source for each control may change as often as 3 times per second. When I have just a single image in my list, life is grand and my app is responsive. When I start having 4 or 5 my application child images, my app starts grinding, I should also mention that I have to a Bitmap to BitmapSource conversion for each new and or updated image that I have.
How should I update my children controls Source property while keeping my application as responsive as possible?
Here is the current code in my ViewModel:
public BitmapSource CameraBitmapSource
{
get
{
Application.Current.Dispatcher.BeginInvoke((Action)delegate
{
BuildImageSource();
}, DispatcherPriority.Background);
return this.cameraBitmapSource;
}
}
BuildImageSource() is where I fetch my new bitmap and convert to a BitmapSource and then assign to my private cameraBitmapSource object.
Because you use Dispatcher.BeginInvoke, you're doing all the work on the UI thread, which makes your app unresponsive. You should build the images on a separate thread. The easiest way to do that is to make the binding asynchronous, and call your BuildImageSource method directly.
ViewModel
public BitmapSource CameraBitmapSource
{
get
{
BuildImageSource();
return this.cameraBitmapSource;
}
}
XAML
<Image Source="{Binding CameraBitmapSource, IsAsync=True}" />
Just remember to Freeze the ImageSource in BuildImageSource so that it can be used on the UI thread (DependencyObjects can only be used on the thread that created them, unless they're Freezable and frozen)
Related
I have a custom control in the early stages of development as I endeavour to learn about wpf custom control development. The custom control inherits from ItemsControls which gives me access to an ItemsSource property to which I am binding an enumerable collection.
Currently I have a simple two project solution comprising my custom control in one and a test project in the other to test the former. In my test project I have a simple mainwindow onto which I have put my custom control and bound its ItemsSource.
<WpfControls:VtlDataNavigator x:Name="MyDataNavigator"
ItemsSource="{Binding ElementName=MainWindow, Path=Orders}" />
In the loaded event of the main window (which implements INotifyPropertyChanged) I instantiate the Orders collection. The customcontrols gets initialised before the main window loads but I can see from examining the Live Visual Tree in visual studio that once the main form loads the custom controls Items Source property is indeed set to Orders. Now of course I'd actually like to count the orders and have my custom control display that (it's a simple data navigator so what I'm after is the record count). I know how to get the count but how do I know when the itemsSource has changed so that I can react to it and get the count. There's no itemsSourceChanged event that I can see.
I've seen this blog article, but I'm wondering if there is a more straightforward approach to this as it seems such an obvious thing to want to know about.
You can do that using OverrideMetaData.
Try this:
public class Class1 : ItemsControl
{
static Class1()
{
ItemsSourceProperty.OverrideMetadata(typeof(Class1),
new FrameworkPropertyMetadata(null, OnItemSourceChanged));
}
private static void OnItemSourceChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Why you haz Changed me!");
}
}
The ItemsSource is a DependencyProperty, and when creating DPs you can optionally specify a "property changed" event. Unfortunately ItemsSource is locked away in the base class, so I started wondering if there might be a way to add your own event to an existing DP. I came across this article that looks promising. Basically you would do something like this (untested so read the article!):-
var dpd = DependencyPropertyDescriptor.FromProperty(
VtlDataNavigator.ItemsSourceProperty,
typeof(VtlDataNavigator));
if (dpd != null)
{
dpd.AddValueChanged(
vtlDataNavigatorInstance,
delegate
{
var count = VtlDataNavigatorInstance.ItemsSource.Count; // Or whatever...
});
}
My WPF UI is keeping all of the images I use in memory. Below is the relevant retention graph. Using the ANTS Memory Profiler 8.7, I have established that none of my code is holding onto any these objects. I have written code so that multiple request for the same image create only one image, but that leaves me with the problem that there are enough images within my application to crash it when they are all loaded simultaneously. I turned off this code when I ran this memory profile. I need to flush these images. I have even resorted to manually calling GC.Collect which did not reduce the memory used. Something is holding these images and it is not my code.
Here is the code for how I expose the BitmapImage to then be bound to Image.Source. This does not contain my image-to-path dictionary caching service that is now turned off.
public BitmapImage Image
{
get
{
var image = new BitmapImage();
image.BeginInit();
image.UriSource = a_url;
image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
image.EndInit();
image.Freeze();
return image;
}
}
// The XAML
<Image Source="{Binding Image, Mode=OneWay}"/>
NOT A DUPLICATE
I have determined that the BitmapImage is being held on to and have explicitly invoked GC.Collect which did nothing. The problem is not the GC. I also always Freeze my BitmapImage objects when I create them in code. This is not my first time around.
I can see from the object graph that you have a class called ImageAnimationController, which is holding a reference to the Image control that uses your BitmapImage as its source by a DependencyPropertyDescriptor. The ImageAnimationController uses the DependencyPropertyDescriptor to subscribe to change notifications of the Image control's Source DependencyProperty. When the ImageAnimationController class is disposed it will unsubscribe from the DependencyPropertyDescriptor notifications.
It looks like this ImageAnimationController class is a component of an open source library called WPF Animated GIF. I can't see how you are using this library as you have not included the source, but I imagine that somewhere you have either something like this:
ImageBehavior.SetAnimatedSource(img, image);
Or this:
<Image gif:ImageBehavior.AnimatedSource="{Binding Image}" />
I am not familiar with this library or your code, but I imagine that you will need to ensure that this attached behaviour is correctly detached and disposed of.
A project I'm working on has some rather complex XAML that is noticeably affecting visual performance. Quite a few controls are collapsed for the initial state; however, since their XAML is parsed and visual /logical trees built, it's very slow to show what amounts to an almost blank object.
It looks like (and would like confirmation here) that using a ContentControl with an initial state of Collapsed and then embedding the desired control as a DataTemplate for that ContentControl, will defer loading of the desired control in the DataTemplate until the ContentControl is made visible.
I've built a generic DeferredContentControl that listens for the LayoutUpdated event of the main UI control (in general whatever element it is that I want to appear quickly), and when the first LayoutUpdated event of that UIElement fires, I used the Dispatcher to flip the visibility of the DeferredContentControl to true, which causes the control in the DeferredContentControl's DataTemplate to instantiate. By the time the user has reacted to the initial view of the screen (which is now fast), the "slow to load" (but still collapsed) control in the data template is ready.
Does this seem like a sound approach? any pitfalls? It seems to work well in testing both for Silverlight and WPF, and while it doesn't make things any faster it gives the perception of being as much as 50% snappier in my specific scenario.
I had the same problem (in a Silverlight project), and solved it in nearly the same way. It proved to be working as expected, have not encountered any pitfalls yet.
When you need to control the point in time when xaml is parsed and view elements are instantiated you can always use DataTemplates (not necessarily in cunjuction with ContentControl). You can call DataTemplate.LoadContent() to instatiate it, you don't have to switch the visibility of a ContentControl (although internally this will result in such a LoadContent call).
Have a look at my implementation if you want, it can even display a static text message while the heavier VisualTree is build:
<DeferredContent HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<DeferredContent.DeferredContentTemplate>
<DataTemplate>
<MyHeavyView/>
</DataTemplate>
</Controls:DeferredContent.DeferredContentTemplate>
<TextBlock Text="Loading content..."/>
</Controls:DeferredContent>
and the code
public class DeferredContent : ContentPresenter
{
public DataTemplate DeferredContentTemplate
{
get { return (DataTemplate)GetValue(DeferredContentTemplateProperty); }
set { SetValue(DeferredContentTemplateProperty, value); }
}
public static readonly DependencyProperty DeferredContentTemplateProperty =
DependencyProperty.Register("DeferredContentTemplate",
typeof(DataTemplate), typeof(DeferredContent), null);
public DeferredContent()
{
Loaded += HandleLoaded;
}
private void HandleLoaded(object sender, RoutedEventArgs e)
{
Loaded -= HandleLoaded;
Deployment.Current.Dispatcher.BeginInvoke(ShowDeferredContent);
}
public void ShowDeferredContent()
{
if (DeferredContentTemplate != null)
{
Content = DeferredContentTemplate.LoadContent();
RaiseDeferredContentLoaded();
}
}
private void RaiseDeferredContentLoaded()
{
var handlers = DeferredContentLoaded;
if (handlers != null)
{
handlers( this, new RoutedEventArgs() );
}
}
public event EventHandler<RoutedEventArgs> DeferredContentLoaded;
}
I have an application that was implemented using the Telerik RadGridView control and Caliburn.Micro MVVM framework. Because of some performance problems, I needed to implement the Telerik VirtualQueryableCollectionView in place of the direct control-to-ObservableCollection binding that was being used. The original code has the ItemsSouce property of the RadGridView was bound to the Prices property of the view model. I had to eliminate that binding and this in the code-behind:
public PricingView(PricingViewModel vm)
{
InitializeComponent();
var dataView = new VirtualQueryableCollectionView()
{ LoadSize=20, VirtualItemCount = vm.Prices.Count };
dataView.ItemsLoading += (sender, e) =>
{
var view = sender as VirtualQueryableCollectionView;
if (dataView != null)
{
view.Load(e.StartIndex, vm.Prices.Skip(e.StartIndex).Take(e.ItemCount));
}
};
this.PricesGridView.ItemsSource = dataView;
}
Since this code only deals with UI specific functionality and it is specific the the view implementation, I am comfortable that this code belongs in the code-behind rather than the ViewModel as it would be a departure from ther MVVM pattern to put a reference to VirtualQueryableCollectionView in the ViewModel. The part that I am not happy with is passing the reference to the ViewModel into the constructor of the View. Is there a good way to get the reference in the code-behind without having to pass the reference in the constructor?
Or is there a better way to do all of this?
My application is implemented with MVVM Light, in my case I used the VirtualQueryableCollectionView class in the ViewModel instead the View.
I did so because I think this class is very similar to the ObservableCollection although it is not part of the core classes.
Actually, VirtualQueryableCollectionView is not limited to the Telerik controls but many other standard controls like the ListView.
The fetch is in my case implemented in the Model.
void MainViewModel()
{
this.Traces = new VirtualQueryableCollectionView<MyEntityClass>()
{
// ViewModel also manages the LoadSize
LoadSize = this.PageSize,
VirtualItemCount = myModel.TotalCount
};
this.Traces.ItemsLoading += (s, args) =>
{
this.Traces.Load(args.StartIndex,
myModel.FetchRange(args.StartIndex, args.ItemCount));
};
}
Not sure what "performance problems" means, but I'm going to assume that means that when you fill the collection from the UI thread it blocks the application long enough it appears unresponsive.
There are two common solutions for this. First is to simply fill your collection from a background thread.
The naive implementation is to simply push the loading onto a ThreadPool thread, then use the Dispatcher to marshall the calls to add items to the ObservableCollection onto the UI thread.
A nicer approach (one that doesn't involve the ViewModel at all) is to use asynchronous bindings. You configure the fallback to some value that indicates to the user you are loading. Sometimes (depending on the situation) you can use a PriorityBinding to gradually fill your UI.
Other alternatives are to load and cache your data beforehand while displaying a splash screen. They're a bit different in WPF, it isn't like the old "display this form for a bit while I do work, then show the main form" mode of winforms. And, of course, there is always the classic data pagination. Its tough to code, but effective. Actually, I should say its tough in the UI. Its easy now in code (database.Skip(pageNumber * pageSize).Take(pageSize)).
I use a browse for files dialog to allow a user to select multiple images. If a lot of images are selected, as expected it takes a bit. Below is an example of what I do with the selected images. I loop through the filepaths to images and create an instance of a user control, the user control has an Image control and a few other controls. I create the instance of this control then add it to a existing stackPanel created in the associating window xaml file. The example just below works fine, but I'm trying to understand BackGroundWorker better, I get the basics of how to set it up, with it's events, and pass back a value that could update a progress bar, but because my loop that takes up time below adds the usercontrol instance to an existing stackPanel, It won't work, being in a different thread. Is BackGroundWorker something that would work for an example like this? If so, what's the best way to update the ui (my stackpanel) that is outside the thread. I'm fairly new to wpf and have never used the BackGroundWorker besides testing having it just update progress with a int value, so I hope this question makes sense, if I'm way off target just let me know. Thanks for any thoughts.
Example of how I'm doing it now, which does work fine.
protected void myMethod(string[] fileNames) {
MyUserControl uc;
foreach (String imagePath in fileNames) {
uc = new MyUserControl();
uc.setImage(imagePath);
stackPanel.Children.Add(uc);
progressBar.Value = ++counter;
progressBar.Refresh();
}
}
below this class i have this so I can have the progressBar refresh:
public static class extensionRefresh {
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement) {
uiElement.Dispatcher.Invoke(DispatcherPriority.Background, EmptyDelegate);
}
}
Check out this article on
Building more responsive apps with the Dispatcher
Now that you have a sense of how the Dispatcher works, you might be surprised to know that you will not find use for it in most cases. In Windows Forms 2.0, Microsoft introduced a class for non-UI thread handling to simplify the development model for user interface developers. This class is called the BackgroundWorker
In WPF, this model is extended with a DispatcherSynchronizationContext class. By using BackgroundWorker, the Dispatcher is being employed automatically to invoke cross-thread method calls. The good news is that since you are probably already familiar with this common pattern, you can continue using BackgroundWorker in your new WPF projects
Basically the approach is
BackgroundWorker _backgroundWorker = new BackgroundWorker();
// Set up the Background Worker Events
_backgroundWorker.DoWork += _backgroundWorker_DoWork;
_backgroundWorker.RunWorkerCompleted += _backgroundWorker_RunWorkerCompleted;
// Run the Background Worker
_backgroundWorker.RunWorkerAsync(5000);
// Worker Method
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Do something
}
// Completed Method
void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Doing UI stuff
if (e.Cancelled)
{
statusText.Text = "Cancelled";
}
else if (e.Error != null)
{
statusText.Text = "Exception Thrown";
}
else
{
statusText.Text = "Completed";
}
}
Using a BackgroundWorker alone won't solve your issue since elements created during the DoWork portion will still have originated from a non-UI thread. You must call Freeze on any objects you intend to use on another thread. However only certain UI objects will be freezable. You may have to load in the images as BitmapImages on the background thread, then create the rest of your user control on the UI thread. This may still accomplish your goals, since loading in the image is probably the most heavyweight operation.
Just remember to set BitmapImage.CacheOption to OnLoad, so it actually loads up the image when you create the object rather than waiting until it needs to be displayed.