I have an application that loads some objects and displays them in a grid. One of those objects is a preview image which could be either a local image file or an image file on a web location. Right now it is working but the performance isn't very good when scrolling because whenever a new item scrolls into view it has to load the preview which takes a second or two.
What I am trying to accomplish is that the box for the preview would show up and start loading the preview on a background thread when it is scrolled into view. When the image is loaded it would notify (via property change event in the view model) and the image would come into place. This way the UI remains responsive and the preview is just blank until it loads and then it shows up. I have the getter for the bound property starting the backgroundWorker if the image isn't already cached, and the worker routine is as follows (_previewCache is the backing property for the bound preview):
void bw_LoadPreview_DoWork(object sender, DoWorkEventArgs e)
{
BitmapImage _tempCache = new BitmapImage(new Uri(PreviewImagePath, UriKind.RelativeOrAbsolute));
if (_tempCache.CanFreeze)
{
_tempCache.Freeze();
Dispatcher.CurrentDispatcher.Invoke((Action)(delegate { _previewCache = _tempCache; }));
}
}
This seems to work pretty well on local files, though it takes a little longer than I would expect for the images to appear, but the UI remains responsive. However when the image is a web path, it always ends up as _tempCache.CanFreeze = false so it skips it and doesn't load. If I try to do the invoke without freezing, it says it can't use the object because it's owned by a different thread.
What am I missing? How do you load a web based image this way? Or is there a better way to approach this issue that I'm not seeing? Any help is greatly appreciated.
Related
I have a multi-threaded application created on top of C#.
In my application i have 1 form with two panels.
pnlSearch
pnlSlide
Each one is controlled by each Thread:
TH_Slide
TH_Search
TH_Slide is called on the Form_OnLoad where i can display all information of my objects inside the pnlSlide.
while(true)
{
// do some infinite work to slide objects information.
Thread.Sleep(20000); // display for 20 secs.
}
The pnlSearch is hidden and will show up when TH_Search is called this is handled when the mouse moved inside the form. And inside the pnlSearch there is txtFilter textBox and a UserControl object as a holder to display the searched information dynamically.
Now the problem is when i type in the txtFilter of a pnlSearch and load the information from my database and display it to the UserControl.
The TH_Slide.ThreadState becomes WaitSleepJoin and would not running anymore hence the slide information in pnlSlide wont make any changes.
NOTE: Cross Thread Operation is handled using delegates therefore it would not throw any errors.
My application is now perfectly working by setting the
TH_Slide.IsBackground = true
TH_Slide.Priority = ThreadPriority.Highest
TH_Search.Priority = ThreadPriority.Normal
I am new to multi-threading programming and i can't fully understand why this algorithm solves my problem.
Friends,
I am assigning the Background of RootFrame to application resources, It works when you explicitly write the Resource name like below
App.RootFrame.Background = (System.Windows.Media.ImageBrush)App.Current.Resources["Theme_6"];
but If I use below it doesn't work:
string themeName = "Theme_6";
App.RootFrame.Background = (System.Windows.Media.ImageBrush)App.Current.Resources[themeName];
Is it possible to use the 2nd options in wp8?
Thanks!
Panorama control (and I think Pivot controls as well) has some problem supporting late binding for background images.
When you hard-code the image paths, there is no problem displaying a static background image.
To assign background images 'on the fly' you should follow these steps;
Create a Property (MainBackGroundImage) within your associated
ViewModel, that implements INotifyPropertyChanged interface (if you
are using MVVM pattern you already have this infrastructure).
Assign any image path (this can be a remote URL as well) to this
property whenever you want to change the background image.
In your View hook up to your ViewModel's property changed event and
update the layout of the control where background image is going to
appear:
void viewModel_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
if (e.PropertyName == “MainBackGroundImage”)
{
this.MainPanorama.UpdateLayout();
}
}
You might perhaps take a look at my open-source WP8 application where I did achieve dynamic background images.
I have a WPF page with a lot of textboxes on it and all of them have to be loaded from xml data (this takes a bit too long).
var tRange = new TextRange(tbox.Document.ContentStart, tbox.Document.ContentEnd);
using (var stream = new MemoryStream(value))
{
tRange.Load(stream, DataFormats.Xaml);
}
Is there a way to load the content of the texboxes as they are displayed to the user?
They are all in a ScrollView, but only a few of them are visible at any time.
As a note: I tried to do the loading in a Loaded event and in the OnRender event of my textbox, but in both cases the method was called for all of the items on the page, not just the visible ones.
One thing to note here, is that you are loading the information sequentially. Because of that, the UI has no time to render between each load of the TextBox. As such, all of the TextBox controls are displayed with the loaded information once the sequential process has been completed.
If you want to display the loaded information in the TextBox controls incrementally, it may be worth running the loading process on a separate thread or BackgroundWorker (tutorial). This way, when you finish loading some information for a TextBox, you can call back to the Dispatcher thread and update the TextBox. Then, WPF will have chance to render the information while you are continuing the laborious loading process in the background (it also makes for a nicer user experience, rather than getting the appearance of a frozen application).
Try using a background worker, That way the texboxes will keep getting filled up in the backgroud, and the ui will stay responsive.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
//Fill Textboxes
};
worker.RunWorkerAsync("XMLPATH");
I have a form that is loading quite an amount of data from SQL server. Below is the code that will provide a good hint:
private void BranchCenter_Load(object sender, EventArgs e) {
//Combo boxes:
LoadCities();
LoadCoordinators();
LoadComputerSystems();
//Actual entity
LoadBranch();
}
private void LoadCities() {
//LINQ2SQL to load data. ~5000 records.
}
private void LoadCoordinators() {
//LINQ2SQL to load data. ~50 records.
}
private void LoadComputerSystems() {
//LINQ2SQL to load data. ~550 records.
}
private void LoadBranch() {
LoadBranchInit();
LoadBranchDetails();
LoadBranchTimings();
LoadBranchServices();
LoadBranchLocumsHistory();
LoadBranchJobs();
LoadBranchNotes();
}
private void LoadBranchInit() {
//LINQ2SQL to load the Branch object based upon the Branch ID
}
private void LoadBranchDetails() {
//LINQ2SQL to load basic branch stuff. 38 fields. Mixed editors.
}
private void LoadBranchTimings() {
//LINQ2SQL to load timings info into 80 date-time controls
}
private void LoadBranchServices() {
//LINQ2SQL to load services offered at branch info into 20 check-boxes controls
}
private void LoadBranchLocumsHistory() {
//LINQ2SQL to load branch history info into grid control. Always increasing # of rows :(
}
private void LoadBranchJobs() {
//LINQ2SQL to load branch jobs info into grid control. Always increasing # of rows :(
}
private void LoadBranchNotes() {
//LINQ2SQL to load branch notes info into grid control
}
The UI is a form with a tab controls. each detail from above goes to a tab page. I need to load and show the form to user as fast as possible. once the form is shown, i need to launch a series of background workers to get the data for each page.
I have been trying to mess with the background worker but unable to understand it's usage. I end up getting message of "different thread attempted to access control on your main thread... or something like that..."
The ideal situation would be to have a progress bar loading the data on every tab and the tab becoming inter-actable once the respective background worker finishes.
Any strategy or advice? Thanks for the read.
If each type of data is displayed on just one page, I would move the code so that each corresponding type of data is loaded the first time each tab page is loaded. That way the data loading work will be distributed over a longer time, and perhaps avoided altogether if the user does not navigate to all tabs during a session.
When loading a page, you could use a BackgroundWorker or any other asynchronous mechanism. In the tab page you can have two panel controls. One that contains the UI for the page, and that has Visible = false when the form is loaded, and one containing a label with a text like "Loading, please wait...". When the data is loaded for the page, and the UI is updated, toggle visibility on the two panels to show the UI. That way the form will still be responsive while the data is loading, and since you load data for only one page at a time, load times should be fairly short.
the ideal solution is that you do not really load data in the form load event.
Let the form load properly and entirely so the UI render completes.
After that you could display a progress bar wherever you want and you could execute the actual data loading, either synchronous or asynchronous.
if you don't need everything loaded at the same time I would also consider loading data only the first time a tab is accessed, so if the user never clicks on the last tab, for example, you didn't load anything for it.
Create a BackgroundWorker for each of the tabs. In the Load event of the form, disable all tabs and call the RunWorkerAsync() method for each of the background workers.
In each DoWork event handler, load the data required for the associated tab page from the database into a data table and set the Result property of the DoWorkEventArgs to the data table.
Note: In the DoWork event handler you should NOT update any UI control since it is operating in a different thread. You should only retrieve the data from the database and set the Result property.
In the RunWorkerCompleted event handlers, you can access the data table, that was retrieved in the DoWork event handler, by getting the Result property of the RunWorkerCompletedEventArgs. Then you can set the properties of the UI controls accordingly and then enable the tab page associated with the current background worker.
The problem here is that the DoWork method of the BackgroundWorker cannot touch the GUI.
It has to use the BackgroundWorker.ReportProgress( int progress, object state) to invoke a method on the GUI thread that is free to update the GUI.
The important point here is that the GUI controls should only be updated from the GUI thread itself, else random exceptions will occur here and there in your program.
You can't mess around with the UI from any other thread apart from the UI thread; that's the source of your errors. If you want more information on how to properly update a UI element from a different thread, you'll need to use Invoke. This question on Stack Overflow might help you out.
Yes, BackgroundWorker is great for this.
I think that you do it wrong because you want to change your form in the DoWork event.
However, you should only collect results of this in e.Results of that event
Then in RunWorkerCompleted you use its e.Results to change the form.
The TabControl already does lazy-loading.
Do yourself a favor and place the contents of each tab on a UserControl.
To address just the problem you are encountering with the background worker thread, it sounds like you may be trying to access UI components from the thread itself. To do this from a non-UI thread you will need to make use of Control.Invoke rather than trying to access the components directly.
Okay, I face a new scenario. I shifted to Visual Studio 2010 and ran into Task.Factory.StartNew(() => myMethod());
This is what I have so far:
private void LoadLocum() {
var TaskInit = Task.Factory.StartNew(() => LoadLocumInit());
TaskInit.Wait();
var TaskDetails = Task.Factory.StartNew(() => LoadLocumDetails());
TaskDetails.Wait();
var TaskQualifications = Task.Factory.StartNew(() => LoadLocumQualifications());
//Enable Qualification TabPage automatically whenever TaskQualifications is complete
Parallel.Invoke(
() => LoadLocumComputerSystems(),
() => LoadLocumOtherInfo(),
() => LoadLocumEmergencyContacts(),
() => LoadLocumDistanceRates(),
() => LoadLocumDocuments(),
() => LoadLocumNotes(),
() => LoadLocumBannedStatus()
);
}
The first two steps (tasks) are critical. Now, the Tab Pages of the Tab are disabled. I need to enable them based upon their related task completion. I can find an event to subscribe to that indicates a certain task is completed or not.
Okay. Massive response in just about no time. Love this place. Jumping from link-2-link, I came face to face with Reactive Extensions for .NET (Rx). Can this be a good alternate as well? Can't find a tutorial on how to use Rx in my scenario.
I am trying to use a background worker in order to retrieve a large amount of data from the database without stalling the main thread. This seems to work well, except that when it comes to update the UI, the update freezes the screen. Relevant code as follows:
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lvwTest.BeginUpdate();
lvwTest.Items.Clear();
// Populate the UI
foreach (TestItem ti in testData)
{
ListViewItem lvi = lvwTest.Items.Add(ti.Value1);
lvi.SubItems.Add(ti.Value2);
}
lvwTest.EndUpdate();
}
The update takes around 2 - 3 seconds, for which time the screen is locked. I realise that only the main thread can update the screen, but is it possible to load this data into memory in some way (in a background thread or another instance of a listview or something) and then just display it? All I want to happen is for the program to simply refresh the data without taking up time in the main thread.
I recommend loading the data into memory and using a virtual mode ListView. That way, you only create the ListViewItem objects as they are needed.
If you have to load really huge amount of data into UI it will require time and it will block our app. The option is smart scrolling or pagination. You load all data but you put it piece by piece upon user request.
Since most of the above are good advice, but don't really solve your immediate problem, here is another approach:
This will update your GUI and keep it responsive. Assuming you are in WinForm App?
Application.DoEvents();
this.Refresh();
Nevertheless, this does not mean that maybe you should not listen to the ideas from above :-)
In addition to virtualization, I would recommend breaking the items into batches of, say, 100 and adding each batch in its own message. That way, the UI has a change to process other messages whilst the batches are being added to the ListView.
In other words, all the RunWorkerCompleted handler does is queue the first batch for adding in a separate message. The adding method will then add the items and then queue the next batch. This will continue until there are not more items left to add. At that point, you would re-enable the relevant portion of your UI (the ListView).