We have a rather large WPF business application and I am working on a retool of an existing WPF FixedPage/FixedDocument report.
It's a somewhat busy ecosystem. We have a built-in forms generator, with lots of different controls you can put on (think like a mini built-in visual studio). All that works fine. You fill in the form on the screen, and then you can print out (to XPS) the identical copy to standard 8.5x11 paper.
In the code, we break out this report into vertical chunks. Say each chunk would be an inch or two tall on a printed piece of paper. This is how we handle pagination. If the next chunk is too tall for the page, we do a NewPage() and repeat. As I mentioned, this was working fine.
WPF has an enormous learning curve and I've been going back over old code and refactoring things and happily working with DataTemplates, strongly typed ViewModels, and generic ContentControls in order to reduce the size of our code. The on-screen forms generator still works, but the FixedDocument report has gotten weird.
Going back to those vertical slices, we print the user's forms to paper as individual Grid controls. Nothing fancy. Each grid (as I mentioned above) may be an inch or two high, containing any random mixture of checkboxes, radiobuttons, textblocks, and so on.
When the grids contained these stock (standard) MS WPF controls, I could do this all day long:
System.Windows.Controls.Grid g = .....
g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));
And get back proper sizes, i.e. 100 x 67.
Now, sometimes the grids have just one control - a header if you will (i.e. "This Month's Schedule). The only child control added to that grid is a ContentControl.
The ContentControl is simply bound to a ViewModel:
<ContentControl Content="{Binding}" />
There's then two DataTemplates in the resource dictionary that picks up this binding. Here, I'll show that:
<UserControl.Resources>
<w:MarginConverter x:Key="boilerMargin" />
<DataTemplate DataType="{x:Type render:BoilerViewModel}">
<render:RtfViewer
Width="{Binding Path=Width}"
TextRTF="{Binding Path=Rtf}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type render:Qst2NodeViewModel}">
<ContentControl Content="{Binding Path=BoilerVm}">
<ContentControl.Margin>
<MultiBinding Converter="{StaticResource boilerMargin}">
<Binding Path="NodeCaptionVm.Height" />
<Binding Path="NodeLeft" />
</MultiBinding>
</ContentControl.Margin>
</ContentControl>
</DataTemplate>
</UserControl.Resources>
The ContentControl will pick up that bottom-most datatemplate. That template will then in turn use the smaller one above.
The fancy converter just sets a margin. It may be fugly to read, but this all displays correctly on the screen within the parent usercontrol. It's all the right size and justification and all that.
On the printed report side (XPS), I have to create these controls in code and measure them to see if they'll fit on the current FixedPage. When I go to do this step: (on the grid containing this ContentControl)
g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));
I get back 0,0 size. Even though it should be like 730x27 for instance. Again, on the screen, hosted in a UserControl, this all works fine. Just trying to instantiate it and measure it purely in code fails. I've confirmed that the control is added to the grid, has its row and col set, has been added to the Children collection, etc...
If I prepend those two statements with an UpdateLayout call, like this, then it works:
g.UpdateLayout(); //this fixes it
g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));
I've been reading that UpdateLayout is expensive and to be avoided, and I'd rather not be calling this on each grid section before I add it to my FixedPage of the FixedDocument report. There could be dozens or even hundreds of iterations. And, again, if the Grid has regular WPF controls in it, without any ContentControls and fancy finding and looking up datatemplates, the measuring works fine without the UpdateLayout call.
Any advice? Thank you!
I just don't understand why it became necessary to start calling it once I started utilizing the Xaml engine. It almost feels like I'm being punished for using the advanced features.
Its complicated to explain that but let me try using plain words... In wpf everything works with dispatcher. Futhermore like you may already know dispatcher deals with tasks ordered by priority.
For example first a control is being initalized, then binding is being triggered, then values are updated, in the end all that is being measured.. etc etc
What you managed somehow is by setting all those contentcontrol inside contentcontrol stuff, you screwed up that order
Calling UpdateLayout basically forces dispatcher to finish its pending work in layout so you can work with clean layout afterwards
Screwing with dispatcher is quite common in wpf since some controls or values may be added later which ends in remeasuring things.
In your case you seem to be creating all at once in one method call without letting dispatcher take a breath. Therefore you need UpdateLayout method to normalize dispatchers queue.
I hope this helps you. You can also solve your issue by using Dispatcher.BeginInvoke.
UpdateLayout does not work in my case. I had to wait until dispatcher finishes processing of the layout tasks.
toPrint.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Application.Current.Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle);
toPrint.Arrange(new Rect(new Point(0, 0), toPrint.DesiredSize));
I found another article about this approach.
Related
I am currently developing a GUI for an Ising model (german wikipedia because only the picture on the right really matters) which should consist of approx 200x200 spin elements. I implemented this in the following way:
<UniformGrid Name="grid" .... />
and added a rectangle for every spin in the code behind which I update if the value of the spin changes. This somehow was very slow and I changed it so it uses Binding
<ItemsControl Name="IsingLattice" ItemsSource="{Binding Spins}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Name="grid" ...
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle Fill={Binding Color} ...
but this is again - very slow. I tried to debug and improve it for 3 days now but no success so far.
Now the question is: Is my approach wrong? What should I use instead if so? If not - how could I improve the performance?
If it's relevant I will update this post with some details of the implementation of my model.
Edit: It should be possible to change single spins by interacting with the elements. This could be done with a transparent layer on top of the actual graphics though so maybe not that hard anyway.
You could write a single custom element (derived from FrameworkElement) that stores the spin data internally then renders the data in one pass by overriding the OnRender method:
public sealed class IsingModel : FrameworkElement
{
readonly bool[] _spinData = new bool[200 * 200];
protected override void OnRender(DrawingContext dc)
{
// use methods of DrawingContext to draw appropriate
// filled squares based on stored spin data
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
// work out which "cell" was clicked on and change
// appropriate spin state value in Boolean array
InvalidateVisual(); // force OnRender() call
}
}
This approach should be faster than having several thousand individual elements. How much faster I don't know.
ItemsControl is meant to repeat UI controls based on datasource. UI control must be customizable, responsive when layout changes, and interactive. Neither of this is your case.
You actualy want to render just a picture.
This - seems to be a Bitmap, so you should threat it as a Bitmap. Instead of ItemsControl use Image and instead of ItemsSource use WritableBitmap as a Source of Image.
When it comes to your original code, it could have couple of performance bottlenecks:
Generation of your classes you used as a ItemsSource can take some time
UniformGrid needs to measure size and calculate position of each element. This can take a while. With Canvas you could achieve better results
It can take some time to ItemsControl to generate 40 000 items from DataTemplate. You could create those rectangles manualy and add it to canvas in code behind
Binding have performance cost. If each of your item is databound, then your binding needs to be evaluated 40 000 times. Instead of binding you could set the properties manualy in code behind.
Using canvas and no binding I was able to render the grid in just 500miliseconds. However, using WritableBitmap or other "pixel based" approach you could display much larger grids.
A GUI, any kind of GUI technology, being it WPF or Windows Forms or anything else, is not meant to handle heavy graphics. It's meant to be easy to develop simple graphics.
If you need graphic power (like dynamically updating 40.000 cells) then you need a framework for graphics. Most likely, a gaming framework will do, pick one of your choice.
Alternatively, you could try to emulate it yourself by only binding to a single picture and drawing that picture yourself when a cell changes. Maybe that's enough, you will have to test that.
Have you considered using the BitmapCache to improve rendering speed?
My understanding is that this can significantly improve rendering speed when drawing complex controls, or when having many instances of the control on screen at the same time. You would want to enable the cache at the grid level, not on each individual spinner.
I'm working on a LineGraph control which consists many DependencyProperties that affect how the control should display its data. For example, the control contains the following properties to affect its axes:
AxisStroke - Color of the axes.
AxisThickness - Stroke thickness of the axes.
It also contains properties for display numbers & tick marks
VerticalTicks - True/False to indicate whether or not ticks appear along the vertical axis
HorizontalTicks - True/False to indicate whether or not ticks appear along the horizontal axis
VerticalMin - Minimum value on the vertical axis (numeric)
VerticalStep - The distance in between each vertical tick
VerticalMax - Maximum value on the vertical axis (numeric)
HorizontalMin - Minimum value on the horizontal axis (numeric)
HorizontalStep - The distance in between each horizontal tick
HorizontalMax - Maximum value on the horizontal axis (numeric)
And many more properties exist to allow for different line styles on a single graph (LineColor, LineThickness, DataPointShape, and DataPointIcon to name a few).
My goal is to be able to call out my LineGraph in XAML to insert it into a Window. I would like to be able to specify each of these settings inside the XAML as well, and see the new rendered image of the control in the WPF designer.
Now, given there is a lot of geometric shapes to render on the LineGraph, I though using a Canvas would be a good choice to render the data. Unfortunately, when I'm working in XAML, I cannot perform computations for the locations of shapes based on the control's width & height.
And yes, the shapes' locations must be computed because the data points for the graph are dynamic and the tick-related information is dynamic. Not to mention, I would like to display the actual values along each axis of the LineGraph.
So, I thought I might be able to display this control as if I was doing the rendering in C# code. Other windowing frameworks sometimes provide a Render method that can be used for laying out all of the sub-components.
Doing this, however, doesn't seem possible since WPF relies heavily on XAML for the visual appearance of controls. Also, requiring that the WPF designer must display the LineGraph based on the properties and data specified, it doesn't seem like C# code would solve the problem.
I suppose my questions are these:
How can I render data dynamically inside of a WPF control?
Am I able to specify in C# how my control is rendered, allowing the WPF designer to reflect it?
Side Note:
I've done quite a bit of research, but I am only finding information on how to implement more simple types of controls. If you know of any references that contain information on this topic, please feel free to post them in addition to your answers. I will be more than happy to learn how to do this completely.
EDIT:
I've created a graph using Excel to elaborate what the LineGraph control might look like if it has correct data and properties.
I will answer this based on my experience on implementing custom built graphing libraries in WIN32, WinForm, WPF, WinCE, WP8+WinRT, ....and even on a FPGA :)
It's extremely difficult to implement one from scratch. It may seem easy at first but you will run into a lot of "What should I do if this happens?". For example, in your above graph it seems you got a DataPoint # (5,100) it graphs it pretty well. But lets say, I add another DataPoint # (5.000000005, 0). How would you handle that in your code? Would you say that each pixel on the graph represents an exact value on the X-Axis, or does each pixel represent a range of X-Values?
I would recommend that you use an already establish library to do what you want to do unless you need something very specific like lets say you need horizontal cursors on the graph (think Tektronix Oscilloscope) and you need to calculate some values in between the two cursors.. then maybe you need to implement your own custom one or build on top of an open source one.
So, if you are still adamant of creating your own custom control here are answers to your questions.
How can I render data dynamically inside of a WPF control?
You can use a WriteableBitmap and create your own primitive drawing library from that. After you're done rendering, set it as the ImageSource of your control.
Or you can use WriteableBitmapEx which has GDI like drawing functions already implemented for you.
WriteableBitmapEx CodePlex Page, I also think you can just get it from NuGet as well.
You can also use a <Canvas> and add UI elements to that as well.
Am I able to specify in C# how my control is rendered, allowing the WPF designer to reflect it?
This depends on how you create your controls, but yes you can create Properties in your custom control that will appear in the Designer. Allowing you to change it thus updating the display. I would read a lot of tutorials about writing your own custom user control library. They can explain it better than I can in a SO answer. If you implement the properties correctly it should like so.....
Full Size Image: http://i.stack.imgur.com/pmevo.png
After changing the Number of Rows from 15 to 10 and the starting Y offset to -1 (thus moving the graph up and making the rows a lot taller)
Full Size Image: http://i.stack.imgur.com/0RKnA.png
I need to create a windows store application with e reader functionality. I have to provide a fluid reading experience. Currently, my knowledge of displaying text consists of dropping textblock or textbox on the UI. I do not know how to display large amount of text to create the E Reader experience i am looking for. Which control or combination of controls should i use?
Your text rendering options are
XAML controls
TextBlock
RichTextBlock
WebView + WebViewBrush
DirectWrite - what all the above ultimately use. You can use it with C# through SharpDX.
The controls might be a bit cumbersome to use at times if you need to do measurements etc. and might not give you as much power or performance as DirectWrite does, but will give you support for text selection, copying to clipboard etc. (make sure IsTextSelectionEnabled is set to true).
For measuring text dimensions for paged display in a TextBlock - create a TextBlock in code behind and call Measure() and Arrange(), then get ActualWidth/ActualHeight to get the measurement.
Read Charles Petzold's Principles of Pagination article.
Consider Rapid Serial Visual Presentation (RSVP) speed-reading method used in the ReadQuick app.
I have a Hebrew calendar app where each day is a UserControl. I have 6 labels in that control for the English date, the Hebrew date, Jewish holidays and some other user-defined data. When scrolling, the labels' content changes as the date value for the UserControl goes up or down a week. The scrolling is noticeably slower than Microsoft Outlook Calendar, and profiling reveals that the part taking the longest is updating the label contents, which is not handled by my code.
Is there some way I can make this go faster? MS Outlook seems to have a comparable number of text fields, and the scrolling is smooth.
TextBlocks were not noticeably faster than Labels, but Glyphs gave my calendar whiplash.
Replacing this
<TextBlock Padding="5"
FontFamily="Narkisim"
FontWeight="Bold"
FontSize="20"
Text="{Binding HebrewDate}"/>
with this
<Glyphs Name="HebrewDate"
Margin="5"
StyleSimulations="BoldSimulation"
FontUri = "/Fonts/nrkis.ttf"
FontRenderingEmSize = "20"
UnicodeString = "5771 ןושח ה"
Fill = "Black"/>
made scrolling super fast.
Some notes:
Glyphs do not support binding, so I had to give each one a name and update them in the code behind, like so:
HebrewDate.UnicodeString = zman.HebrewDate;
Glyphs don't have Layout functionality so Hebrew text was coming out backwards. I had to preprocess the Hebrew strings with a reversing function. Even after reversing, the Hebrew vowel points came out misaligned, so I retained Labels for those strings which use vowels.
I can't be sure but it is possible that MS Outlook was coded in something faster than WPF, perhaps using DirectX to show the graphics rapidly.
Otherwise I might suggest toning down on the number of bindings updating at once, I would suggest using an additional thread to gradually update the labels as and when there are spare cycles instead of all at once, which might be causing your stuttering.
To go along with the previous answer, I recommend the background worker. Utilize the background worker for your most time consuming operation that gets executed during the scroll.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
I have a Pivot which contains a WebBrowser control that practically takes up the whole page (appart from the Pivot header of course).
I would like to figure out how to make the WebBrowser control allow for the user to swipe left/right to activate the Pivot control. Currently it just pans the WebBrowser control left/right
Can this be done??
Thank
While I cannot tell you exactly how to pass the swipes to the pivot, I can tell you how to do a part of the job: how to catch/analyze/disable custom gestures over the WebBrowser.
If I remember correctly, in the 7.0:
the WebBrowser component consisted almost only of an internal TileHost wrapped in some grids/borders
the TileHost did all the work related to processing touch events
the TileHost did it completely internally (in the native layer), without the .Net seeing any manipulation-events (I think), or at least it ignored all the attempts to handle/override the manipulation-event on the upper layer. The WebBrowserInterop class was mostly empty in these matters.
Now, in the 7.5 that I have (maybe on 7.1 too, I dont know), it seems that the MS is working really hard on some WebBrowser manipulation problems --- I think they are working towards having the scrolling/swiping fully processed by the .Net layer. They have written a special class PanZoomContainer and injected them into the VisualTree of WebBrowser's internal template. The WebBrowserInterop was greatly enriched with many tunnels for event notifications. The WebBrowserInterop hooks into PanZoomContainer's ManipulationEvents, then passes them to the native layer. Also, it listens to events/commands from the native layer, called for example "ZoomAndScroll" or "ShowSIP" - and mostly passes them back to the PanZoomContainer. The idea is crystal clear right? They have rewired the event handling from completely-internal to a bit of spaghetti, but have achieved passing them through the PanZoomC.
Now, whats in that for me/us?
It is the PanZoomContainer, whose Mani-Events are inspected. The TileHost does not capture them now. In this version of the WebBrowser control, it's VisualTree consists of some borders, grids, a PanZoomContainer and a TileHost (the renderer). It is similar to that:
WebBrowser
PanZoom
ContentPresenter
Border/Name="border" <- you want this one
TileHost
I've skipped a few Borders and Grids, they are mostly irrelevant to the problem. Now, if the PanZoomContainer's Mani-Events are listened to, let's block them!
Using VisualTreeHelper, just dig deeper and deeper until you find a FrameworkElement.Name=="border". This is the border that wraps the TileHost that is the "renderer" that takes 99% space of the control. Be warned that there's a ContentPresenter, so you may have to wait until the controltemplate gets instantiated (ie. Loaded/LayoutUpdated).
Once you have your hands on that Border, attach all Mani-Event handlers to it: started, delta and completed. PanZoom is a normal xaml/silverlight/.net/etc control, so it actually obeys e.Handled = true :) Now, if you want to disable ie. vertical scrolling, filter the Delta and Completed events for Translation.Y<>0. If you want to disable tapping but leave srolling/panning - filter X==0&Y==0.
And that was the easy part.
The hard part is to experiment with filtering on different Start/Delta/Stop and adjusting the behaviour to your likes.
Although it might look very nice and tempting, this will NOT get you any real/nice results easily. For example, I wrote "if you want to disable vertical scrolling, then set a filter 'if y==0 then e.handled=true' ". Great? easy? Not!
Assume we want to "disable bouncy horizontal panning" while leaving "vertical scrolling". or vice versa, whatever, it is only an example:
Here's a small rectangular device with a sensitive touchscreen. Please make such a vertical swipe/pan/drag on the screen, that the resulting X-compound will be ZERO. If you set such filter, it will be almost impossible to it properly. Your users will want to kill you for forcing them to retry-that-vertical-scrolling for five or more times, until they make a perfect vertical swipe.
Of course you can make it not ==0, but leave some small margin. ok. But if you make the margin too big, the control will catch the intermediate offaxis movement and make a tiny horizontal pan also.. After a few unlucky vertical swipes, the total horizontal pan may accumulate from those small leftovers will accumulate and the diplacement maybe will be noticeable.
But there's some another vile side effects:
Saying shortly, you have commited e.Handled=true. The event is GONE. Dead. Deased. if you just wanted the WebBrowser to SKIP for example horizontal swipes, so that the outer (Pivot) control notices them and processes..... whoops. The event is GONE. Earlier, the TileHost/PanZoomC have extinguished the events, now you have it done yourself. Sounds like a bad joke, eh?
Fortunatelly:
since you have attached your handlers to the bottommost "border", they may not only block the events, but may also actually listen&publish them elsewhere. That is, if those handlers detect an interesting movement, they may e.Handled=true on it, but at the same time they can notify your custom objects about that discovery, and ie. start your storyboards.
mani-events are at hand, but there is also a second layer that listens to the manipulations: the GestureListener/GestureService from the Silverlight Toolkit. It reports events after they are handled by mani-events, but it reports them with no regard to any e.Handled=true that were set on them. It is completely separate gesture-listening mechanism, and you can also use it to detect manipulations that were 'cancelled'
.. and so the fun goes like that and maybe even a little further.
This is similar to putting a Map inside a Pivot - which is discussed here - http://mine.tuxfamily.org/?p=111 - already mentioned in quite a few questions - https://stackoverflow.com/search?q=mine.tuxfamily.org
In general, the advice seems to be usability based:
try to avoid putting controls which use Touch inside the PivotItem's
As an aside, if you are just using the web browser control for a very small amount of static html (so you don't need any scrolling at all) then you could just remove HitTesting from the web browser.
I do not know WP7 Pivot, but are there any Preview* events on the Pivot control that allow you to handle the touches and mark them as processed?
Call the below method and pass your parameter as PivotControl x:name and WebBrowserControl x:name to this method.
Here the WebBrowserControl is placed in second pivot item i.e. Pivot Index is 1 and I am trying to swipe left or right and reach to pivot index 2 or 1 respectively.
public static void SwipteLeftRight(Microsoft.Phone.Controls.Pivot pivotControl, Microsoft.Phone.Controls.WebBrowser webBrowserControl)
{
var gesListener = GestureService.GetGestureListener(webBrowserControl);
gesListener.Flick += ((sen, args) =>
{
if (args.Direction == System.Windows.Controls.Orientation.Horizontal)
{
if (args.HorizontalVelocity < 0)
{
if (((Microsoft.Phone.Controls.PivotItem)(pivotControl.SelectedItem)).Header.ToString().Trim() == "Pivot Item name")
{
pivotControl.SelectedIndex = 2; //Next Pivot item
}
}
else if (args.HorizontalVelocity > 0)
{
if ((Microsoft.Phone.Controls.PivotItem)(pivotControl.SelectedItem)).Header.ToString().Trim() == "Pivot Item name")
{
pivotControl.SelectedIndex = 0; // Previous Pivot Item
}
}
}
});
}
It worked for me. Cheers