Inserting Image to FlowDocument - c#

I'm working on a wpf application. I want to create a FlowDocument object and print it. As the creation step takes some seconds and freeze the UI, I move my code to new thread. The problem is that I need to set an Image in the FlowDocument and need to create Image UIElement, but UI Controls cannot be created in background threads !
I've also tried so many Dispather.Invoke() scenarios, but they catch exception about object owner thread.
I wonder is there any other methods to insert image into the FlowDocument? Or is it possible to create Image UIElement in background thread?
any suggestion would be appreciated.
P.S : Some Example Code =>
BitmapImage bitmapImage = SingletonSetting.GetInstance().Logo;
Image v = new Image() { Source = bitmapImage };
currnetrow.Cells.Add(new TableCell(new BlockUIContainer(v)));
Image v = ((App)Application.Current).Dispatcher.Invoke(new Func<Image>(() =>
{
BitmapImage bitmapImage = SingletonSetting.GetInstance().Logo;
return new Image() { Source = bitmapImage};
}));
currnetrow.Cells.Add(new TableCell(new BlockUIContainer(v)));

If you don't need to modify the BitmapImage, then you can freeze it and use it on the UI thread.
// Executing on non UI Thread
BitmapImage bitmapImage = SingletonSetting.GetInstance().Logo;
bitmapImage.Freeze(); // Has to be done on same thread it was created on - maybe freeze it in the Singleton instead?
Application.Current.Dispatcher.Invoke(() => {
// Executing on UI Thread
Image v = new Image() { Source = bitmapImage };
currnetrow.Cells.Add(new TableCell(new BlockUIContainer(v)));
});
After chatting with you, what you really needed to do was run your task in a STA thread, since you were making UI controls on it.
How to do that? See this answer:
Set ApartmentState on a Task

Related

Bringing large image into view at runtime

Here is the problem.
I have a view, that should display an image and some controls.
User add new images, changes some options and click "finish".
Images are large and very large (400-1500 MB Tiff)
User should see the preview of image, but it is ok if it loading for 10-15 sec or even more, he have a job for this time.
Image is binding through MVVM pattern like simple string (file will be always in local folder)
<Image Name="ImagePreview" Source="{Binding SFilePathForPreview,
FallbackValue={StaticResource DefaultImage},
TargetNullValue={StaticResource DefaultImage}}"
HorizontalAlignment="Center" Width="200" Height="200"
VerticalAlignment="Center" />
Problem is that all is hangs when user try to add a file for loading time.
I understand that this case should be solved through multithreading - but have no idea how to implement this.
I tryed to update image from view in different thread like this:
Thread newThread = new Thread(LazyLoad);
newThread.Name = "LazyLoad";
newThread.Start(SFilePathForPreview);
public void LazyLoad(object SFilePath)
{
try
{
string path = (string)SFilePath;
BitmapImage t_source = new BitmapImage();
t_source.BeginInit();
t_source.UriSource = new Uri(path);
t_source.DecodePixelWidth = 200;
t_source.EndInit();
t_source.Freeze();
this.Dispatcher.Invoke(new Action(delegate
{
ImagePreview.Source = t_source;
}));
}
catch
{
//...
}
}
But anyway at point
ImagePreview.Source = t_source;
everything hangs up until image fully loaded.
Is there a way to load a preview in the background and show it without those terrible hangs?
The probably most simple way of asynchronously loading an image is via an asynchronous Binding. You would not have to deal with Threads or Tasks at all.
<Image Source="{Binding Image, IsAsync=True}"/>
A possible view model could look like shown below, where you must make sure that the Image property getter can be called from a background thread.
public class ViewModel : ViewModelBase
{
private string imagePath;
private BitmapImage image;
public string ImagePath
{
get { return imagePath; }
set
{
imagePath = value;
image = null;
OnPropertyChanged(nameof(ImagePath));
OnPropertyChanged(nameof(Image));
}
}
public BitmapImage Image
{
get
{
lock (this)
{
if (image == null &&
!string.IsNullOrEmpty(imagePath) &&
File.Exists(imagePath))
{
using (var stream = File.OpenRead(imagePath))
{
image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.DecodePixelWidth = 200;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
}
}
}
return image;
}
}
}
As you already mentioned, you are blocking the UI thread with the image load. You can use a WriteableBitmap class instance as the source for your Image. This will let you load the image on a background thread or async task. Here is a quick guide (not mine) on the issue.
https://www.i-programmer.info/programming/wpf-workings/527-writeablebitmap.html
Another option would be using priortybinding with the highest priorty to the full image and a lower priority to the faster-loading preview image. MS has documented priority binding here:
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-prioritybinding

Change background image source on button click in wpf forms

My default background image is "lobby.jpg" and when I click the "Lights" button I want it to swap with "lobby1.jpg" and vice versa. These images are stored in "obj\Debug\Images\".
Also I'd like to implement relative(?) imagesource uris so that I can access the images on any machine (without using the whole uri, just the "obj\Debug\Images\").
Edit: So the main issue seems to be that I tried changing the window background without realising that it was getting "covered" by the grid background of the page. So what I did is I set the main window background to "lobby.jpg", I made the grid background invisible and used the code from the answer to swap between the 2 backgrounds.
You can use AppDomain basepath to exe (this is are simplest way)
var basePath= AppDomain.CurrentDomain.BaseDirectory;
var imageDirPath = $"{basePath}\\Images\\";
Example:
bool clicked = false;
private void button_Click(object sender, RoutedEventArgs e)
{
var basePath = AppDomain.CurrentDomain.BaseDirectory;
var imageDirPath = $"{basePath}\\Images\\";
if (clicked)
image.Source = new BitmapImage(new Uri(imageDirPath+ "lobby.jpg"));
else
image.Source = new BitmapImage(new Uri(imageDirPath + "lobby1.jpg"));
clicked = !clicked;
}

Understanding why TPL Task can update UI withOUT FromCurrentSynchronizationContext

I am doing some TPL in VS2012, WPF with MVVM. I have a question that I think I know the answer to but wanted to know for sure. Consider this snippet:
TaskCanceller = new CancellationTokenSource();
TaskLoader = Task<object>.Factory.StartNew(() =>
{
//Test the current query
DataRepository dr = new DataRepository(DataMappingSelected);
string test = dr.TestMappingConnection();
if (test.IsNotNullEmpty())
throw new DataConnectionException(test);
//Create the CSV File
DataQueryCsvFile CsvFile = new DataQueryCsvFile();
CsvFile.FileName = IO.Path.GetFileName(FilePath);
CsvFile.FilePath = IO.Path.GetDirectoryName(FilePath);
CsvFile.DataMapping = DataMappingSelected;
CsvFile.DataBrowserQuery = DataQueryHolder;
//Allow for updates to the UI
CsvFile.PageExportComplete += (s, e) =>
{
if (TaskCanceller.IsCancellationRequested)
(s as DataQueryCsvFile).IsSaveCancellationRequested = true;
StatusData = String.Format("{0} of {1} Complete", e.ProgressCount, e.TotalCount);
StatusProgress = (100 * e.ProgressCount / e.TotalCount);
};
CsvFile.SaveFile();
return CsvFile;
});
I have a class DataQueryCsvFile. Its intent is to create a CSV text file based off a passed set of query parameters the results of which can be very large. So the export "paginates" the table produced by the query so it does not blow the users memory. Among its members is an Event called PageExportComplete which is called whenever a "Page" is written to a file - say 1000 records at a time. The code below uses this event to update a progress indicator on the UI.
The progress indicators (StatusData and StatusProgress) are declared in the VM with appropriate Notification to let the View know when they are changed. For example:
public string StatusData
{
get { return _StatusData; }
set { NotifySetProperty(ref _StatusData, value, () => StatusData); }
}
private string _StatusData;
Here is my question - as is, this works very well. But why since I did NOT declare the Task to run or update via the UI thread (FromCurrentSynchronizationContext) in a ContinueWith.
Is it because the MVVM pattern? In other words, because the properties being updated are local to the VM and because they have the notification to update the View and because of the lose coupling via bindings its works? Or am I just lucky due to the circumstances and I should go through the trouble of declaring a ContinueWith to update progress on the UI thread?
UI related stuff can only be updated from UI thread whereas any CLR property binded to UI can be updated from background thread, they don't have thread affinity issue.
Like you posted in your sample, you are only updating View model properties from background thread which is perfectly fine but if you try updating Progress bar text directly, it will fall miserably since progressBar is UI component and can only be updated from UI thread.
Say you have TextBlock binded to Name property in ViewModel class:
<TextBlock x:Name="txt" Text="{Binding Name}"/>
If you try to update text directly, you will get famous thread affinity issue:
Task.Factory.StartNew(() => txt.Text = "From background");
But in case you try to update ViewModel Name property, it will work fine since no UI stuff is access from background thread here:
ViewModelClass vm = DataContext as ViewModelClass;
Task.Factory.StartNew(() => vm.Name = "From background");

Databound WPF, Multipage Tiffs, Memory Streams and Performance

I have a databound object within a WPF control that is 'previewing' a mutlipage tiff.
The object has a public PreviewImage, and CurrentPreviewPage.
It has a private PreviewPages which is a collection of MemoryStreams (each representing a page of the Tiff).
Upon the get of the PreviewImage (the first time) this code runs:
if (PreviewPages.Count == 0)
{
Image myImg = System.Drawing.Image.FromFile(_LocalFile);
for (int i = 0; i < (NumberOfPages); i++)
{
myImg.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
System.IO.MemoryStream ms = new System.IO.MemoryStream();
myImg.Save(ms, System.Drawing.Imaging.ImageFormat.Tiff);
PreviewPages.Add(ms);
}
}
The previous code takes about 10 seconds to run for a 1100KB 17 page TIFF. There must be a better way of handling this.
Afterwards, this is called:
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 1000;
//bi.UriSource = new Uri(fiTemp.FullName);
bi.StreamSource = new System.IO.MemoryStream(PreviewPages[CurrentPreviewPage - 1].ToArray());
bi.EndInit();
_PreviewImage = bi;
Now, after the initialization this code works fantastically (it can change pages as fast as you can drag a bound slider). Any help would be much appreciated.
Load your first page on the primary thread and then additional pages in the background - see BackGroundWorker. Only primary thread can access the UI. On the BackGroundWorker you need to decide if you are going to get the pages 2-x one at a time or all at once. The user cannot get to page 2 until you bring page 2 to the UI thread. I would implement cancel. If the user gives up you don't want to tie up CPU.

WPF Dispatcher issue when adding tab items to tab control

I'm getting an error when running this code:
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { tabControl1.Items.Add(tbItem); }));
the tabcontrol1 is hard coded into the xaml and the tab item/s are built at runtime.
I'm getting an error:
TargetInvocationException was
unhandled Exception has been thrown by
the target of an invocation.
I'd love to hear any thoughts on this.
Thanks
UPDATE
the inner exception:
{"The calling thread cannot access
this object because a different thread
owns it."}
the full method code:
TabItem tbItem = new TabItem();
tbItem.Header = worker;
Grid grid = new Grid();
ListBox listBox = new ListBox();
listBox.HorizontalAlignment = HorizontalAlignment.Stretch;
listBox.VerticalAlignment = VerticalAlignment.Stretch;
listBox.ItemsSource = datasource.Where(i => i.Category == worker);
grid.Children.Add(listBox);
tbItem.Content = grid;
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { tabControl1.Items.Add(tbItem); }));
The method is called with this:
Thread newThread = new Thread(myMethod);
newThread.SetApartmentState(ApartmentState.STA);
newThread.Start();
ANOTHER UPDATE
This works:
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal,
(Action)(() =>
{
TabItem tbItem = new TabItem();
tbItem.Header = worker;
//Grid & ListBox(within tab item)
Grid grid = new Grid();
ListBox listBox = new ListBox();
listBox.HorizontalAlignment = HorizontalAlignment.Stretch;
listBox.VerticalAlignment = VerticalAlignment.Stretch;
listBox.ItemsSource = datasource.Where(i => i.Category == worker);
grid.Children.Add(listBox);
tbItem.Content = grid;
tabControl1.Items.Add(tbItem);
}));
as you can see your tbItem is created on different thread, even through it tries to dispatch it back to the TAbControl's main gui thread.
why not extract out the part that takes longer (which you are usign thread for) and once you got result back, continue with creating tbItem and adding it to TabControl in the GUI thread
Example:
tabControl.Dispatcher.Invoke calls below function with dataSoruce result it gets
List<string> result = null;
tabControl.Dispatcher.Invoke((Action<IEnumerable<string>>)ProcessResult, result);
public void ProcessResult(IEnumerable<string> datasource)
{
//this is where you do creating TabItem and assigning data source to it and adding to TabControl.
}
Haven't compiled, pls check syntax
Check the InnerException property to find out the reason. TargetInvocationException is just a wrapper by the wpf runtime. Your lambda probably throws, but without the actual exception you can't tell.
edit
You are creating your TabItem in a different thread, thus the GUI thread can't access it, even though you use the dispatcher for the actual adding. As you already posted with your last snippet, you have to create the TabItem in the GUI thread. Only do the calculation in a different thread, and once the result returns, do the actual creation of the TabItem in the GUI thread (if necessary via the Dispatcher).
The problem is that you're creating your UIElements on a separate thread. This is not allowed.
You can do your processing on a background thread (the call to datasource.Where(i => i.Category == worker);), but unfortunately, every UI element needs to be constructed and used entirely on the main user interface thread.
In your case, this means constructing your ListBox and Grid on the UI thread, inside the Dispatcher call.
I would suggest rewriting this as:
// Load the data on the background...
var data = datasource.Where(i => i.Category == worker);
// Do all work on the UI thread
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal,
(Action)(() =>
{
TabItem tbItem = new TabItem();
tbItem.Header = worker;
//Grid & ListBox(within tab item)
Grid grid = new Grid();
ListBox listBox = new ListBox();
listBox.HorizontalAlignment = HorizontalAlignment.Stretch;
listBox.VerticalAlignment = VerticalAlignment.Stretch;
listBox.ItemsSource = data;
grid.Children.Add(listBox);
tbItem.Content = grid;
tabControl1.Items.Add(tbItem);
}));

Categories

Resources