how to use Drawingvisual in wpf? - wpf

i have a screen that shows thousands of points and refresh rate is 10 ms.
first i had problem because rendering was slow and jittery.
i searched internet people suggest me to convert shapes to visual because shapes have a lot of events and is heavy to render. i changed the points to visuals like this:
public class MyVisualHost : FrameworkElement{
// Create a collection of child visual objects.
private VisualCollection _children;
public MyVisualHost()
{
_children = new VisualCollection(this);
...
}
// Provide a required override for the VisualChildrenCount property.
protected override int VisualChildrenCount
{
get { return _children.Count; }
}
// Provide a required override for the GetVisualChild method.
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= _children.Count)
{
throw new ArgumentOutOfRangeException();
}
return _children[index];
}}
the performance still is not acceptable. the question is what is the difference between shapes and FrameworkElement. both have lots of events that make them heavy to render. i want something that doesnt have events. what can i do?!
actually i want to add these visuals to canvas and give them their positions using canvas.setLeft and canvas.setTop. how to do this without inheriting from FrameworkElement?

Related

Custom WPF canvas with multiple underlying visuals

Found interesting article describing how to create custom canvas control by exposing methods Add and Remove for underlying visuals of the Panel class. This way I could create universal canvas that can accommodate absolutely any way to draw on it, GDI+, Canvas, Drawings, etc. For example, the first layer would be Bitmap, second Canvas, third DrawingVisual, etc.
For simplicity, I'd like to extend existing Canvas, so I could have original behavior provided by default Canvas control + could create as many additional Visuals as I want to.
Here is what I have now.
public class VisualCanvas : Canvas
{
protected IList<Visual> _visuals = null;
protected override int VisualChildrenCount => _visuals.Count;
protected override Visual GetVisualChild(int index) => _visuals.ElementAtOrDefault(index);
public VisualCanvas()
{
_visuals = new List<Visual>();
_visuals.Add(new DrawingVisual());
//(_visuals[0] as DrawingVisual).RenderOpen();
}
public void AddVisual(Visual visual)
{
_visuals.Add(visual);
base.AddVisualChild(visual);
base.AddLogicalChild(visual);
}
public void DeleteVisual(Visual visual)
{
_visuals.Remove(visual);
base.RemoveVisualChild(visual);
base.RemoveLogicalChild(visual);
}
}
Unfortunately, DrawingVisual that I add in the constructor doesn't make this control to act like original Canvas because 2 methods that I overridden seem to expect different kind of Visual, not DrawingVisual.
How do I make this control work like original Canvas?
Found similar question on MSDN. Appears to be it's not enough to override only 2 methods to keep all children in one list of visuals. Default canvas collection InternalChildren also needs to be taken into account.
This implementation seems to keep default canvas behavior + new visuals, but may have unpredictable behavior because 2 class collections share the same index. Looks like it would be easier to extend this class with new properties for each visual type rather than trying to override existing ones. Feel free to post better ideas and implementations.
public class VisualCanvas : Canvas
{
protected IList<Visual> _visuals = new List<Visual>();
protected override int VisualChildrenCount => _visuals.Count + InternalChildren.Count;
protected override Visual GetVisualChild(int index) => _visuals.ElementAtOrDefault(index) ?? InternalChildren[index - _visuals.Count];
public void AddVisual(Visual visual)
{
_visuals.Add(visual);
base.AddVisualChild(visual);
base.AddLogicalChild(visual);
}
public void DeleteVisual(Visual visual)
{
_visuals.Remove(visual);
base.RemoveVisualChild(visual);
base.RemoveLogicalChild(visual);
}
}

WPF rendering is too slow

I am experiencing a strange problem trying to use WPF to render a number of polylines (64 polylines about 400-500 vertices in each on a 2300x1024 Canvas). Polylines are updated every 50ms.
For some reason my application UI becomes very sluggish and almost unresponsive to user input.
I am using to following class to avoid updating the point collection while it is displayed:
class DoubleBufferPlot
{
/// <summary>
/// Double-buffered point collection
/// </summary>
private readonly PointCollection[] mLineBuffer =
{
new PointCollection(),
new PointCollection()
};
private int mWorkingBuffer; //index of the workign buffer (buffer being modified)
#region Properties
//Polyline displayed
public Polyline Display { get; private set; }
/// <summary>
/// index operator to access points
/// </summary>
/// <param name="aIndex">index</param>
/// <returns>Point at aIndex</returns>
public Point this[int aIndex]
{
get { return mLineBuffer[mWorkingBuffer][aIndex]; }
set { mLineBuffer[mWorkingBuffer][aIndex] = value; }
}
/// <summary>
/// Number of points in the working buffer
/// </summary>
public int WorkingPointCount
{
get { return mLineBuffer[mWorkingBuffer].Count; }
set
{
SetCollectionSize(mLineBuffer[mWorkingBuffer], value);
}
}
#endregion
public DoubleBufferPlot(int numPoints = 0)
{
Display = new Polyline {Points = mLineBuffer[1]};
if (numPoints > 0)
{
SetCollectionSize(mLineBuffer[0], numPoints);
SetCollectionSize(mLineBuffer[1], numPoints);
}
}
/// <summary>
/// Swap working and display buffer
/// </summary>
public void Swap()
{
Display.Points = mLineBuffer[mWorkingBuffer]; //display workign buffer
mWorkingBuffer = (mWorkingBuffer + 1) & 1; //swap
//adjust buffer size if needed
if (Display.Points.Count != mLineBuffer[mWorkingBuffer].Count)
{
SetCollectionSize(mLineBuffer[mWorkingBuffer], Display.Points.Count);
}
}
private static void SetCollectionSize(IList<Point> collection, int newSize)
{
while (collection.Count > newSize)
{
collection.RemoveAt(collection.Count - 1);
}
while (collection.Count < newSize)
{
collection.Add(new Point());
}
}
}
I update the working buffer offscreen and then call Swap() to have it displayed. All 64 polylines (DoubleBufferPlot.Display) are added to a Canvas as children.
I used Visual Studio Concurrency Analyzer tool to see what's going on and discovered that after each update the main thread spends 46ms performing some WPF-related tasks: System.Widnows.ContextLayoutManager.UpdateLayout() and System.Windows.Media.MediaContex.Render().
I also discovered that there is another thread that's running almost non-stop rendering
wpfgfx_v0400.dll!CPartitionThread::ThreadMain
...
wpfgfx_v0400.dll!CDrawingContext::Render
...
etc.
I read a number of articles on WPF including this: Can WPF render a line path with 300,000 points on it in a performance-sensitive environment?
and also this article http://msdn.microsoft.com/en-us/magazine/dd483292.aspx.
I am (or my company rather) trying to avoid DrawingVisual since the rest of the project uses WPF shapes API.
Any idea why this is so slow? I even tried disabling anti-aliasing (RenderOptions.SetEdgeMode(mCanvas, EdgeMode.Aliased)) but it did not help very much.
Why does layout update takes so long. Anyone who is an expert in WPF internals?
Thank you very much.
After trying different approaches including DrawingVisual it seems that drawing polylines with so many vertices is too inefficient.
I ended up implementing at approach where I draw polylines only when there 1 or fewer vertices per pixel. Otherwise I render manually to a WriteableBitmap object. This is surprisingly much more efficient.
The fastest way I've found to draw frequently updated geometry is to create a DrawingGroup "backingStore", output that backing store during OnRender(), and then update that backingStore when my data needs to update, by using backingStore.Open(). (see code below)
In my tests, this was more efficient than using WriteableBitmap or RenderTargetBitmap.
If your UI is becoming unresponsive, how are you triggering your redraw every 50ms? Is it possible some of the redraw is taking longer than 50ms and backing up the message-pump with redraw messages? One method to avoid this is to shut off your redraw timer during your redraw loop (or make it a one-shot timer), and only enable it at the end. Another method is to do your redraw during a CompositionTarget.Rendering event, which happens right before the WPF redraw.
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}

How to recognize WPF drawing visuals in UIAutomation?

Our application has a canvas, to which we add the drawing visuals (like lines, polygons etc)
// sample code
var canvas = new Canvas(); // create canvas
var visuals = new VisualCollection(canvas); // link the canvas to the visual collection
visuals.Add(new DrawingVisual()); // add the visuals to the canvas
visuals.Add(new DrawingVisual());
Our goal is to add these visuals to the canvas via automation and validate that they are properly added. We use a framework that is based on Microsoft's UIAutomation.
When using a tool like "Inspect" to inspect the visual structure, I couldnt locate the canvas. Did some research and figured out that you need to override the OnCreateAutomationPeer method from UIElement, and return applicable AutomationPeer object to be able to be able to see that in automation.
Made the change and now I can see the canvas, however I cant still see any of the visuals added under the canvas.
Can anyone help me understand what the issue is?
Things tried / alternatives:
Tried to employ the OnCreateAutomationPeer technique, but the
DrawingVisuals dont derive from UIElement, and I cant add UIElements
to Canvas.VisualCollection.
Image recognition is an option, but we
are trying to avoid it for performance/maintenance considerations.
Only UIElement can be seen from UI Automation (like you have seen, OnCreateAutomationPeer starts from this class, not from the Visual class).
So you need to add UIElement (or derived like FrameworkElement) to the canvas, if you want it to be usable by UIAutomation.
You can create your own class like described here: Using DrawingVisual Objects or with a custom UserControl or use an existing one that suits your need but it must derive from UIElement somehow.
Once you have a good class, you can use the default AutomationPeer or override the method and adapt more closely.
If you want to keep Visual objects, one solution is to modify the containing object (but it still needs to derive from UIElement). For example, here if I follow the article in the link, I can write a custom containing object (instead of a canvas of your sample code so you may have to adapt slightly) like this:
public class MyVisualHost : UIElement
{
public MyVisualHost()
{
Children = new VisualCollection(this);
}
public VisualCollection Children { get; private set; }
public void AddChild(Visual visual)
{
Children.Add(visual);
}
protected override int VisualChildrenCount
{
get { return Children.Count; }
}
protected override Visual GetVisualChild(int index)
{
return Children[index];
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new MyVisualHostPeer(this);
}
// create a custom AutomationPeer for the container
private class MyVisualHostPeer : UIElementAutomationPeer
{
public MyVisualHostPeer(MyVisualHost owner)
: base(owner)
{
}
public new MyVisualHost Owner
{
get
{
return (MyVisualHost)base.Owner;
}
}
// a listening client (like UISpy is requesting a list of children)
protected override List<AutomationPeer> GetChildrenCore()
{
List<AutomationPeer> list = new List<AutomationPeer>();
foreach (Visual visual in Owner.Children)
{
list.Add(new MyVisualPeer(visual));
}
return list;
}
}
// create a custom AutomationPeer for the visuals
private class MyVisualPeer : AutomationPeer
{
public MyVisualPeer(Visual visual)
{
}
// here you'll need to implement the abstrat class the way you want
}
}

How to set scroll position from view model with caliburn.micro?

I have a ListBox in my view, bound to a collection that is dynamically growing. I would like the scroll position to follow the last added item (which is appended to the bottom of the list). How can I achieve this with Caliburn.Micro?
An alternative could be to use the event aggregator to publish a message to the view.
Something like:
Aggregator.Publish(ItemAddedMessage<SomeItemType>(itemThatWasJustAdded));
and in the view:
public class SomeView : IHandle<ItemAddedMessage<SomeItemType>>
{
public void Handle(ItemAddedMessage<SomeItemType> message)
{
// Implement view specific behaviour here
}
}
It depends on what your requirements are but at least then the view is responsible for display concerns and you can still test the VM
Also you could just implement the code solely in the view - since it appears to be a view concern (e.g. using the events that listbox provides)
A behaviour would also be useful but maybe one that's a little less coupled to your types - e.g. a generic behaviour SeekAddedItemBehaviour which hooks listbox events to find the last item. Not sure if the listbox exposes the required events, but worth a look
EDIT:
Ok this may work full stop - you should be able to just attach this behaviour to the listbox and it should take care of the rest:
public class ListBoxSeekLastItemBehaviour : System.Windows.Interactivity.Behavior<ListBox>
{
private static readonly DependencyProperty ItemsSourceWatcherProperty = DependencyProperty.Register("ItemsSourceWatcher", typeof(object), typeof(ListBoxSeekLastItemBehaviour), new PropertyMetadata(null, OnItemsSourceWatcherPropertyChanged));
private ListBox _listBox = null;
private static void OnItemsSourceWatcherPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ListBoxSeekLastItemBehaviour source = d as ListBoxSeekLastItemBehaviour;
if (source != null)
source.OnItemsSourceWatcherPropertyChanged();
}
private void OnItemsSourceWatcherPropertyChanged()
{
// The itemssource has changed, check if it raises collection changed notifications
if (_listBox.ItemsSource is INotifyCollectionChanged)
{
// if it does, hook the CollectionChanged event so we can respond to items being added
(_listBox.ItemsSource as INotifyCollectionChanged).CollectionChanged += new NotifyCollectionChangedEventHandler(ListBoxSeekLastItemBehaviour_CollectionChanged);
}
}
void ListBoxSeekLastItemBehaviour_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems.Count > 0)
{
// If an item was added seek it
ScrollIntoView(e.NewItems[0]);
}
}
protected override void OnAttached()
{
base.OnAttached();
// We've been attached - get the associated listbox
var box = this.AssociatedObject as ListBox;
if (box != null)
{
// Hold a ref
_listBox = box;
// Set a binding to watch for property changes
System.Windows.Data.Binding binding = new System.Windows.Data.Binding("ItemsSource") { Source = _listBox; }
// EDIT: Potential bugfix - you probably want to check the itemssource here just
// in case the behaviour is applied after the original ItemsSource binding has been evaluated - otherwise you might miss the change
OnItemsSourceWatcherPropertyChanged();
}
}
private void ScrollIntoView(object target)
{
// Set selected item and try and scroll it into view
_listBox.SelectedItem = target;
_listBox.ScrollIntoView(target);
}
}
You probably want to tidy it up a bit and also make sure that the event handler for CollectionChanged is removed when the ItemsSource changes.
Also you might want to call it SeekLastAddedItemBehaviour or SeekLastAddedItemBehavior - I tend to keep the US spelling since it matches Microsoft's spelling. I think SeekLastItem sounds like it will scroll to the last item in the list rather than the last added item
You could reference the view in the view model using GetView(). That also couples the view and view model.
var myView = GetView() as MyView;
myView.MyListBox.DoStuff
Another option is to create a behavior. This is an example of how to use a behavior to expand a TreeView from the view model. The same could be applied to a ListBox.
Actually, there is an easier way to achieve this, without any of the above.
Just extend your Listbox with the following:
namespace Extensions.Examples {
public class ScrollingListBox : ListBox
{
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
int newItemCount = e.NewItems.Count;
if (newItemCount > 0)
this.ScrollIntoView(e.NewItems[newItemCount - 1]);
base.OnItemsChanged(e);
}
}
}
}
Then in Xaml, Declare the Location of your extension class as so:
xmlns:Extensions="clr-namespace:Extensions.Examples"
And when you create your listbox, instead of using
<Listbox></Listbox>
Just use your extended class
<Extensions:ScrollingListBox></Extensions:ScrollingListBox>

wpf converter : setting multiple properties

I am used to using converters that return a value per property, such as Foreground color.
Is it possible to have a converter that works with multiple properties?
such as: Foreground, Background, Font-Weight, Font-Size
How can I create one converter (or less than 4) that could set multiple properties?
No, converters aren't designed for that. You could possibly go down the attached behaviour route and set the properties, based on a bound dependency property (I assume) on attach?
Edit: behaviours are part of the Blend SDK, the basic structure of what you want is:
public class MyBehavior : Behavior<TextBlock>
{
//// <-- Dependency property here
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Foreground = CalculateForeground();
this.AssociatedObject.Background = CalculateBackground();
// etc..
}
private Brush CalculateForeground()
{
// Do some calculations based on the dependency property
}
private Brush CalculateBackground()
{
// Do some calculations based on the dependency property
}
protected override void OnDetaching()
{
base.OnDetaching();
// You might want to reset to default here, or just do nothing
}
}

Resources