I have a WPF application that retrieves object 'movement' messages from a EasyNetQ/RabbitMQ message queue. Getting the message works fine as I can see in my logger.
private void btnSubscribeToMessageQ_Click(object sender, RoutedEventArgs e)
{
_logger.Debug("Listening for messages.");
_messageBus.Subscribe<RZMD.Messages.Movement>("test", HandleMovement);
}
Handling the message happens with:
private void HandleMovement(RZMD.Messages.Movement movementMessage)
{
_logger.Debug("Movement: {0}", movementMessage);
AddCirkelToCanvasWithDispatcher(movementMessage);
_movements.Add(movementMessage);
}
The UI is updated as follows by drawing a cirkel on the canvas:
private void AddCirkelToCanvasWithDispatcher(RZMD.Messages.Movement movementMessage)
{
var center = new Point(movementMessage.X, movementMessage.Y);
//var cirkel = new EllipseGeometry() { Center = center, RadiusX = 5, RadiusY = 5 }; <<
// above line causes exception re threads
// System.InvalidOperationException: 'The calling thread cannot access
// this object because a different thread owns it.'
Application.Current.Dispatcher.Invoke( ()=>
{
//if I put new EllipseGeometry() here all is fine
var cirkel = new EllipseGeometry() { Center = center, RadiusX = 5, RadiusY = 5 };
var path = new Path() { Stroke = Brushes.Black, Data = cirkel };
cnvFloor.Children.Add(path);
});
}
What I don't understand is why the call to var cirkel = new EllipsGeometry()... throws an exception when it is placed before the Dispatcher.
There is no problem creating the center Point object of the cirkel before the dispatcher. How is the Ellipse object different?
Is this the right (modern) approach? Or are there better tools such as 'async/await', 'TPL' or 'Parallel Linq'
Also I'm adding the movements to a collection of movements.
Should I investigate using an Observable collection and Notify events to draw the cirkel on the canvas instead?
How is the EllipseGeometry object different?
It is a System.Windows.Threading.DispatcherObject, and hence has thread affinity, i.e. unless it is frozen, it can only be accessed in the thread where it was created.
Instead of creating the EllipseGeometry inside the Dispatcher action, you may freeze it to make it cross-thread accessible:
var cirkel = new EllipseGeometry() { Center = center, RadiusX = 5, RadiusY = 5 };
cirkel.Freeze();
Application.Current.Dispatcher.Invoke(() =>
{
var path = new Path() { Stroke = Brushes.Black, Data = cirkel };
cnvFloor.Children.Add(path);
});
Related
I created an object from points with this code, dynamically:
SolidColorBrush brushColor = (SolidColorBrush)new BrushConverter().ConvertFromString(_brushColor);
PathFigure figures = new PathFigure();
figures.StartPoint = points[0];
points.RemoveAt(0);
figures.Segments = new PathSegmentCollection(points.Select((p, i) => new LineSegment(p, i % 2 == 0)));
PathGeometry pg = new PathGeometry();
pg.Figures.Add(figures);
canvas.Children.Add(new Path { Stroke = brushColor, StrokeThickness = 3, Data = pg });
Now I want to add event handler for this object. Would not be a problem if object is a path or polyline type. I would just add event handler like this:
poly.MouseDown += new MouseButtonEventHandler(poly_MouseDown);
void poly_MouseDown(object sender, MouseButtonEventArgs e)
{
//code
}
Problem is that I have to use Figures and PathGeometry which does not accept MouseDown event handlers. Since they are from System.Windows.Media class and Path/Polyline is from System.Windows.Shapes I can't find a solution to assign right event handler (MouseDown) to my Figure object.
What is the solution or is there any nice workaround solution for this problem? Maybe cast or convert it somehow?
As I see it you're skipping a crucial step.
Declare the Path Object first, give it the event, and then insert it into your Canvas:
SolidColorBrush brushColor = (SolidColorBrush)new BrushConverter ().ConvertFromString (_brushColor);
PathFigure figures = new PathFigure ();
figures.StartPoint = points[0];
points.RemoveAt (0);
figures.Segments = new PathSegmentCollection (points.Select ((p, i) => new LineSegment (p, i % 2 == 0)));
PathGeometry pg = new PathGeometry ();
pg.Figures.Add (figures);
Path pgObject = new Path({ Stroke = brushColor, StrokeThickness = 3, Data = pg });
pgObject.MouseDown+=new MouseButtonEventHandler(poly_MouseDown);
canvas.Children.Add (pgObject);
I have a window with a transparent background (not null). Inside that window, I have a user control, also with a transparent background.
The window receives mouse events, but the user control does not.
If I change the background of the user control from Transparent to #01000000, then the user control starts to receive mouse events. However, controls hosted within the user control (which are themselves visible) never receive mouse events, regardless of the user control's background.
Any ideas?
There must be something handling the event or you have some configuration issue, since it works ok for me: http://share.linqpad.net/ijx3vb.linq. (Get Linqpad free.)
var t = new TextBlock() { Text = "test" };
var uc1 = new UserControl() { Background = new SolidColorBrush(Colors.Transparent) };
uc1.MouseEnter += (s, args) => { t.Text = "UC1"; };
var uc2 = new UserControl() { Background = new SolidColorBrush(Color.FromArgb(01, 00, 00, 00)) };
uc2.MouseEnter += (s, args) => { t.Text = "UC2"; };
var g = new Grid();
g.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
g.RowDefinitions.Add(new RowDefinition());
g.RowDefinitions.Add(new RowDefinition());
g.Children.Add(t);
g.Children.Add(uc1);
g.Children.Add(uc2);
Grid.SetRow(t, 0);
Grid.SetRow(uc1, 1);
Grid.SetRow(uc2, 2);
PanelManager.DisplayWpfElement(g);
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);
I have generated a graph using the tutorial provided by James McCaffrey : http://msdn.microsoft.com/en-us/magazine/ff714591.aspx
Iam able to do so successfully. Also I have added a tooltip as follows:
plotter.AddLineGraph(compositeDataSource1,
new Pen(Brushes.Blue, 2),
new CircleElementPointMarker{ Size = 10.0, Fill = Brushes.Red ,Tooltip="Coordinates"},
new PenDescription("Number bugs open"));
My Question is : how do I display the co-ordinates of the point in tooltip.?
This is how I solved my issue.
EnumerableDataSource<TPoint> edc;
edc= new EnumerableDataSource<TPoint>(List_Of_TPoint);
edc.SetXMapping(x => dateAxis.ConvertToDouble(x.X));
edc.SetYMapping(y => Convert.ToDouble(y.Y));
edc.AddMapping(CircleElementPointMarker.ToolTipTextProperty, s => String.Format("Y-Data : {0}\nX-Data : {1}", s.Y, s.X));
I just added above mapping while creating my EnumerableDataSource.
And then added the edc to plotter.
plotter.AddLineGraph(
edc,
new Pen(Brushes.Transparent, 3),
new CircleElementPointMarker
{
Size = 15,
Brush = border,
Fill = c2
},
null
);
Using the ElementMarkerPointsGraph and CircleElementPointMarker could be very resource expensive. For each Point an Ellipse will be created. Also the Mapping (building the string for Tooltip) will be executed for every point, even if nobody wants to see the tooltip.
So my way is to use a MarkerPointsGraph and set it's Tooltip dynamically.
Xaml:
<d3:MarkerPointsGraph Name="MyMarkerPointsGraph" DataSource="{Binding Values}" ToolTip="Dummy" ToolTipOpening="CircleMarker_OnToolTipOpening">
<d3:MarkerPointsGraph.Marker>
<d3:CirclePointMarker />
</d3:MarkerPointsGraph.Marker>
</d3:MarkerPointsGraph>
and here the Code behind:
private void CircleMarker_OnToolTipOpening(object sender, ToolTipEventArgs e)
{
var pos = Mouse.GetPosition(MyMarkerPointsGraph);
var transform = GetTransform(MyMarkerPointsGraph);
transform.ScreenToData(pos);
var dataPoint = transform.ScreenToData(pos);
MyMarkerPointsGraph.ToolTip = $"Y-Data : {dataPoint.Y}\nX-Data : {dataPoint.X}";
}
public static CoordinateTransform GetTransform(PointsGraphBase graph)
{
if (!(graph.Plotter is Plotter2D))
return null;
var transform = ((Plotter2D)graph.Plotter).Viewport.Transform;
if (graph.DataTransform != null)
transform = transform.WithDataTransform(graph.DataTransform);
return transform;
}
I am using a WPF BackgroundWorker to create thumbnails. My worker function looks like:
private void work(object sender, DoWorkEventArgs e)
{
try
{
var paths = e.Argument as string[];
var boxList = new List<BoxItem>();
foreach (string path in paths)
{
if (!string.IsNullOrEmpty(path))
{
FileInfo info = new FileInfo(path);
if (info.Exists && info.Length > 0)
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 200;
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.UriSource = new Uri(info.FullName);
bi.EndInit();
var item = new BoxItem();
item.FilePath = path;
MemoryStream ms = new MemoryStream();
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bi));
encoder.Save(ms);
item.ThumbNail = ms.ToArray();
ms.Close();
boxList.Add(item);
}
}
}
e.Result = boxList;
}
catch (Exception ex)
{
//nerver comes here
}
}
When this function is finished and before the BackgroundWorker "Completed" function is started, I can see on the output window on Vs2008, that a exception is generated. It looks like:
A first chance exception of type 'System.NotSupportedException' occurred in PresentationCore.dll
The number of exceptions generates equals the number of thumbnails to be generated.
Using "trial and error" method I have isolated the problem to:
BitmapFrame.Create(bi)
Removing that line (makes my function useless) also removes the exception.
I have not found any explanation to this, or a better method to create thumbnails in a background thread.
Lasse, I believe the problem arises because you are performing actions outside of the UI thread that need to be done within the UI thread. Creating UI elements (BitmapImage, BitmapFrame) and adding to UI Containers, I believe, should be done on the UI thread. (Someone correct me if I'm wrong here).
There are a few ways to create those elements on the UI thread without blocking the application for an excessive period of time. The easiest is probably using the BackgroundWorker's ProgressChanged event. ProgressChanged is invoked on the UI thread, which makes it perfect for this situation.
You can use the worker's ProgressChanged event and pass it the path needed to load a thumbnail in the UserState argument.
Thanks for your input. It made start to look for another solutions and I come up with this.
try
{
var paths = e.Argument as string[];
var boxList = new List<BoxItem>();
foreach (string path in paths)
{
using (Image photoImg = Image.FromFile(path))
{
int newWidth = 200;
int width = newWidth;
int height = (photoImg.Height * newWidth) / photoImg.Width;
var thumbnail = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage((System.Drawing.Image)thumbnail))
{
g.DrawImage(photoImg, 0, 0, width, height);
using (var ms = new System.IO.MemoryStream())
{
var item = new BoxItem();
item.FilePath = path;
thumbnail.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
item.ThumbNail = ms.ToArray();
boxList.Add(item);
}
}
}
}
e.Result = boxList;
}
catch (Exception exp)
{
}
Not using any UI elements .. and works nicely.
Thanks.
//lasse