I have a class that have two fields and some methods for drawing:
public class DrawingTool
{
private readonly DrawingGroup _drawingGroup;
private DrawingImage _imageSource;
public DrawingTool()
{
_drawingGroup = new DrawingGroup();
_imageSource = new DrawingImage(_drawingGroup);
}
public DrawingImage GetImageSource()
{
return _imageSource;
}
public void DrawSomething()
{
using (DrawingContext dc = _drawingGroup.Open()) // HERE'S AN ERROR - _drawing group is being used by another thread (InvalidOperationException)
{
// draw something here
}
}
}
This is a WPF project, so I also have an Image:
<Image x:Name="MyImage" Width="320" Height="240"></Image>
I set the source:
_drawingTool = new DrawingTool();
Dispatcher.Invoke(() => MyImage.Source = _drawingTool.GetImageSource());
_device.FrameReady += (sender, args) => Dispatcher.Invoke(() =>
{
_drawingTool.DrawSomething();
});
I'm getting an error from the comment... I tried to invoke DrawSomething() method and use CheckAccess() for _drawingGroup inside DrawSomething() - didn't help. How to make it possible to work?
Thanks for help in advance!
[edit 1.]
Maybe I should use something different than DrawingGroup?
[edit 2.]
No one has an idea? :(
Ok, I found the solution... It was really obvious... I had to initialize whole drawing tool in the main thread (in the main window constructor), then set MyImage.Source (also in the same place). Now everything's gonna work! :)
Related
I'm trying to write a simple WPF program to implement Conway's Game of Life.
My Window consists of a toolbar and a canvas with and embedded Image, on which I'm trying to display a writeable bitmap.
I have a button on the toolbar which, when clicked, updates a single generation and then displays the resulting bitmap on the canvas image. I update the image directly with
img.Source = wbmp;
This works without a problem.
However, I'd like to show the ongoing updates, without having to click the "Update" button each time. So I tried implementing a loop of 10 iterations. However, the updated image is only shown after the 10th iteration is completed (i.e. I don;t see the first 9 generations)
My understanding is that I need to bind the Image control to the Writeable bitmap in order to "force" an update each generation. I've tried this with the code below - but now nothing displays at all. Initially I found that the PropertyChanged event didn't seem to be firing but I had no method assigned so, I added PropertyChanged = delegate {}; (I did this because the Internet told me to!)
I am really not sure where I'm going wrong. I'm rather clueless about WPF and binding in particular. (Much of my code is adapted copy-pasta.) Any help would be greatly appreciated!
<Canvas Name ="canvas" Grid.Row="1" Background="LightGray">
<Image Name="img" Source="{Binding GoLImage}"/>
</Canvas>
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private ImageSource golImage; //writeable bitmap;
public ImageSource GoLImage
{
get { return golImage; }
set
{
golImage = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(GoLImage)));
}
}
}
private void Button_Run10_Click(object sender, RoutedEventArgs e)
{
BitmapPixelMaker bmp = new BitmapPixelMaker(1200, 800);
for (int i = 0; i < 10; i++)
{
goL.UpdateLifeGrid(); //goL is an instance of my class implementing the Game of Life
goL.LifeGridTobmp(bmp);
WriteableBitmap wbmp = bmp.MakeBitmap(96, 96);
//Trying to display bitmap
MyViewModel golDisplay = new MyViewModel();
golDisplay.GoLImage = wbmp; //This doesn't automatically display on each iteration
}
}
The for loop in your Button Click handler blocks the UI thread. And you have not assigned the view model instance to the DataContext property of the view.
Use a DispatcherTimer with a Tick event handler like shown below.
Do not create a new view model instance and a new WriteableBitmap in each cycle, but just modify the existing one - you should therefore change the view model property declaration to public WriteableBitmap GoLImage so that the ModifyBitmap method can access it.
private readonly MyViewModel golDisplay = new MyViewModel();
private readonly DispatcherTimer timer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(0.1)
};
public MainWindow()
{
InitializeComponent();
golDisplay.GoLImage = WriteableBitmap(1200, 800, 96, 96, PixelFormats.Default, null);
DataContext = golDisplay;
timer.Tick += OnTimerTick;
timer.Start(); // optionally, call Start/Stop in a Button Click handler
}
private void OnTimerTick(object sender, EventArgs e)
{
var bmp = new BitmapPixelMaker(1200, 800);
goL.UpdateLifeGrid();
goL.LifeGridTobmp(bmp);
ModifyBitmap(bmp); // write directly to golDisplay.GoLImage
}
An alternative to a DispatcherTimer might be an simple loop over an asynchronous and awaited update call.
Something like shown below, where the Update method would perform the CPU-intensive calculations and then return the new pixel buffer
private async void OnWindowLoaded(object sender, RoutedEventArgs e)
{
while (true)
{
var buffer = await Task.Run(() => game.Update());
bitmap.WritePixels(new Int32Rect(0, 0, game.Width, game.Height),
buffer, game.Stride, 0);
}
}
After making the project simpler, I believe I identified the problem is actually a result the async marshalling.
UPDATE: I made the code simpler to try to figure out what was going on. So here is an update... The Observable collection is being populated on a new thread (async method). I tried moving the assigning of the ItemsSource to after the ObservableCollection is loaded as seen below
async void LoadAllData(object sender, EventArgs e)
{
if (sender != null)
{
App.GeoLocationComplete -= LoadAllData;
}
await ViewModelObjects.NearbyLocations.LoadLocationData();
lvPlaces.ItemsSource = ViewModelObjects.NearbyLocations.GBSLocationDetails;
}
The definition for the data load method is a follows:
public async Task LoadLocationData()
{....}
When I run this code I get the following error:
The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
I know what is causing the error (the data was loaded on a thread other than the UI thread) but I don't know how to fix it. Suggestions?
UPDATE UPDATE: So I believe I have identified the root cause of the problem but have not figured out how to fix it. I started by simplifying my code as follows and it worked.
public nearbyplaces()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
LoadAllData(null, null);
}
void LoadAllData(object sender, EventArgs e)
{
lobj_Places = new ObservableCollection<GBSLocationDetail>()
{
new GBSLocationDetail()
{
Title = "Location 1",
Distance = "20 Miles",
AddInfo = "Something Else",
AttributesTexts="Gay, Bar, Dance"
}
};
lvPlaces.ItemsSource = lobj_Places;
}
HOWEVER, what I need is for the LoadAllData method to be called once I have the GPS location from the device. So in my App.XAML.cs I have the following delegate event declared:
public static Plugin.Geolocator.Abstractions.IGeolocator gobj_RealGeoCoordinator;
public static event GeoLocationCompleteEventHandler GeoLocationComplete;
public static bool gb_WaitingForLocation = true;
Then I have the following code call the event once I get the location back from the device:
private async void ProcessStartupandResume()
{
if (gobj_RealGeoCoordinator == null)
{
gobj_RealGeoCoordinator = CrossGeolocator.Current;
ViewModelObjects.AppSettings.CanAccessLocation = App.gobj_RealGeoCoordinator.IsGeolocationEnabled;
if (!ViewModelObjects.AppSettings.CanAccessLocation)
{
await MainPage.DisplayAlert(ResourceStrings.GetValue("NoLocationServicesTitle"), ResourceStrings.GetValue("NoLocationServicesMessage"), ResourceStrings.GetValue("OKButtonText"));
}
//Only add the events if the object has to be created.
gobj_RealGeoCoordinator.PositionChanged += gobj_RealGeoCoordinator_PositionChanged;
gobj_RealGeoCoordinator.PositionError += (sender, e) =>
{
ProcessException(new Exception(e.Error.ToString()));
};
}
//Set this to null to trigger the first check
ib_GPSReenabled = null;
if (gobj_RealGeoCoordinator.IsListening)
await gobj_RealGeoCoordinator.StopListeningAsync();
gobj_RealGeoCoordinator.DesiredAccuracy = 50;
await gobj_RealGeoCoordinator.StartListeningAsync(10000, 20);
}
private static void gobj_RealGeoCoordinator_PositionChanged(object sender, PositionEventArgs e)
{
var pos = e.Position;
ViewModelObjects.AppSettings.Latitude = pos.Latitude;
ViewModelObjects.AppSettings.Longitude = pos.Longitude;
if (gb_WaitingForLocation)
{
gb_WaitingForLocation = false;
GeoLocationComplete?.Invoke(new object() , null);
}
}
Then in my page I subscribe to the GeoLocationComplete event using the LoadAllData method as seen below. Even when I use a local object and try to set the ItemsSource for the ListView in the code when executed as a result of the event being raised, I receive the error. See code below which subscribed to the event:
public nearbyplaces()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
if (App.gb_WaitingForLocation)
App.GeoLocationComplete += LoadAllData;
else
LoadAllData(null, null);
}
Any suggestions on how to fix this?
OK so I figured it out. I needed to invoke the event on the main thread and I did that with the following code:
Device.BeginInvokeOnMainThread(() =>
{
GeoLocationComplete?.Invoke(new object(), null);
});
After inserting this code, the error was gone. Changing the code back to simply
GeoLocationComplete?.Invoke(new object(), null);
cause the error to occur again. Thus I believe this resolved my problem. Hope this helps someone else. :)
I'm trying to access a database, get an image and return it so it then can be shown in a Border object. (So I'm converting the byte[] data to an image, too)
This process takes enough time to notice that the UI has frozen.
Here's some code:
ImageBrush imgb = new ImageBrush();
imgb.ImageSource = GlobalDB.GetImage(album.name, 400); // Size is 400px, this is the time-consuming part.
AlbumArt.Background = imgb;
I tried using a backgroundworker but that gave me an exception saying I can't call from different threads. I got that exception since the BackgroundWorker.RunWorkerCompleted apparently was owned by a different thread(?)
Adding to that last bit: the Runworkercompleted did this:
AlbumArt.Background = imgb;
I now don't know what to do.
Any suggestions will be appreciated!
RunWorkerCompleted gets called on UI thread only.
Only issue i can see in your code is imgb is created in background thread and WPF has constraint that DependencyProperty source and Dependency object should be created on same thread.
So before assigning imgb to Background call Freeze() on it:
imgb.Freeze();
AlbumArt.Background = imgb;
Reason being freezed objects are allowed to be access across threads. They are not thread affine.
I have to ask. Where is your view model? Rohit Vats's answer will more or less get the job done, but you are not approaching this idiomatically. You should have a ViewModel with code something like this:
public class AlbumViewModel: BaseViewModel // BaseViewModel is your code that implements INotifyPropertyChanged
{
private string name
public string Name
{
get{ return name;}
set
{
if(name != value)
{
name = value;
FirePropertyChanged("Name");
LoadBackgroundImageAsync(value);
}
}
}
private ImageSource backgroundImage;
public ImageSource BackgroundImage
{
get{return backgroundImage;}
private set
{
if(backgroundImage != value)
{
background = value;
FirePropertyChanged("BackgroundImage");
}
}
}
private Task LoadBackgroundImageAsync(string name)
{
var retv = new Task(()=>
{
var source = GlobalDB.GetImage(name, 400);
source.Freeze();
BackgroundImage = source;
});
retv.Start();
return retv;
}
}
Then just bind to the property and let WPF wory about updating the UI.
not sure about what GetImage are doing, but maybe you can enclosure your code into a Task:
Task.Factory.StartNew(() => {
ImageBrush imgb = new ImageBrush();
imgb.ImageSource = GlobalDB.GetImage(album.name, 400);
AlbumArt.Background = imgb;
});
As already suggested in another answer, you may alternatively use an asynchronous task. But you have to dispatch the assignment of the Background property to the UI thread after freezing the ImageBrush.
Task.Factory.StartNew(() =>
{
var imgb = new ImageBrush(GlobalDB.GetImage(album.name, 400));
imgb.Freeze();
Dispatcher.BeginInvoke(new Action(() => AlbumArt.Background = imgb));
});
I have a smal application that should set a image to red or green depending on some tests that are made. The test can take a few second, and each one has a custom control with an image connected to it. When I click start I would like for the first test to be done, show the result by changing the image on that one, and then move on. But as it is now, all tests are made (maybe 10 seconds), then ALL the lights are changing at the same time. How can I force the custom control to update the image during the excecution?
private void start_Click(object sender, RoutedEventArgs e)
{
foreach (TestObject tObj in tObjList)
{
bool testResult = tObj.makeTest();
foreach (TestShower ts in m_TSList)
{
if (tObj == ts.gettObj())
{
if (testResult == true)
ts.setLightOn();
else
ts.setLightOff();
ts.UpdateLayout();
break;
}
}
}
}
public void setLightOn()
{
string strUri2 = String.Format(#"pack://application:,,,/;component/Images/Signal_On.png");
BitmapImage img = new BitmapImage(new Uri(strUri2));
iStatus.Source = null;
iStatus.Source = img;
}
public void setLightOff()
{
string strUri2 = String.Format(#"pack://application:,,,/;component/Images/Signal_Off.png");
BitmapImage img = new BitmapImage(new Uri(strUri2));
iStatus.Source = null;
iStatus.Source = img;
}
you should read into Async and await and perform each test at the same time and await the results (obviously this assumes your tests are not interdependant)
and some form of implementation of
Task.Factory.StartNew(() => {
var result = ts.makeTest();
setLight1(result);
})
Although without knowing more it would seem as though you could perform this using databinding to a ViewModel that implements INotifyPropertyChanged on a List of Test Objects.
but not performing the tests asynchronously is the main cause of your issue
i know external links are not really preferred in SO but here is a tutorial
http://www.youtube.com/watch?v=ZyFL3hjHADs
Run the tests in the background and then use Dispatcher to update UI thread:
For WPF & Net 4.5. you can use TPL
private void start_Click(object sender, RoutedEventArgs e)
{
Task.Run(()=>{
foreach (TestObject tObj in tObjList)
{
bool testResult = tObj.makeTest();
Dispatcher.Invoke(()=>{
foreach (TestShower ts in m_TSList)
{
if (tObj == ts.gettObj())
{
if (testResult == true)
ts.setLightOn();
else
ts.setLightOff();
ts.UpdateLayout();
break;
}
}});
}
}
});
}
I'm trying to generate a Thread for the redrawing-function of my existing poly-drawing. I read here it is possible that UI can be realized in Threads see here LINK but I cant use it on my redrawSingelPoly() function.... Any ideas how I can use redrawSingelPoly() as an thread ?
In my MainWindow.xaml.cs:
Is called when the user press a button on my main window:
private void SingleSelectedMeasurement(object sender, RoutedEventArgs e)
{
System.Windows.Controls.Button button = (System.Windows.Controls.Button)sender;
RedrawSingelMeasurement(Convert.ToInt16(button.Tag));
}
private void RedrawSingelMeasurement(int selectedMeasurement)
{
selectedMeasurement = selectedMeasurement - 1;
for (int i = 0; i < measurements.Length; i++)
{
if (selectedMeasurement != i)
{
measurements[i].draw = false; //block drawing
}
else
{
measurements[i].draw = true; // remove block for drawing
}
}
measurements[selectedMeasurement].redrawSingelPoly();
}
In my Measurement.cs:
public void redrawSingelPoly()
{
Polyline poly = new Polyline();
poly.Stroke = colorBrush;
poly.StrokeThickness = basicLineThick;
//first clean all
coordinateSystem.Children.Clear();
poly.Points = points;
//draw
coordinateSystem.Children.Add(poly);
}
You cannot access to DependencyProperties of DependencyObject (in your case: coordinateSystem) from the thread different to the one it's related to.
If you want to speed up your application, you should create custom control, override its OnRender method and draw your custom graphics there: it will remove a lot of logical and visual tree logic and will work a lot faster.
Ideally one window only can run on one Dispatcher, however you can put different visuals in different threads by HostVisual but in very limited scenarios. Maybe this article can help you:
http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx
What the article you mention talks about is actually just having one thread for each window. Drawing dedicated elements in a different thread is not possible.
private void SingleSelectedMeasurement(object sender, RoutedEventArgs e)
{
var button = (System.Windows.Controls.Button)sender;
Task.Factory.StartNew (
() => OnUi(RedrawSingelMeasurement(Convert.ToInt16(button.Tag))));
}
//here's a sample on how to get despatcher for the ui thread
private void OnUi (Action action)
{
if (_dispatchService == null)
_dispatchService = ServiceLocator.Current.GetInstance<IDispatchService>();
//or _dispatchService = Application.Current.Dispatcher - whatever is suitable
if (_dispatchService.CheckAccess())
action.Invoke ();
else
_dispatchService.Invoke(action);
}