Printing of WPF Window on one page - wpf

I am able to print the current Window using the following code:
PrintDialog printDialog = new PrintDialog();
if (printDialog.ShowDialog().GetValueOrDefault(false))
{
printDialog.PrintVisual(this, this.Title);
}
However if the Window does not fit the page it get truncated.
How do I make the Window fit the Page ?
I guess I need to make a graphics element first and check if this graphics fits the page, but I have found nothing so far.

There is one solution out there that lots of people are reposting as their own. It can be found here:
http://www.a2zdotnet.com/View.aspx?id=66
The problem w/ that is that it does resize your UI. So this next link takes the previous solution and resizes back to the original size when it's done. This does work, although I can't help but to think there's likely a more elegant solution out there somewhere:
http://www.slickthought.net/post/2009/05/26/Visual-Tree-Printing-in-WPF-Applications.aspx
Slickthought.net domain is defunct. Wayback Machine to the rescue.
https://web.archive.org/web/20130603071346/http://www.slickthought.net/post/2009/05/26/Visual-Tree-Printing-in-WPF-Applications.aspx
<Button Content="Print" Command="{Binding Path=PrintCommand}" CommandParameter="{Binding ElementName=ReportPanel}"></Button>
There are two important things to note here. First, I am using a WPF command to start the printing process. You don't have to do it this way, but it lets me tie the presenter to the UI pretty cleanly. The second thing is the CommandParameter. It is passing in a reference to the the ReportPanel. ReportPanel is just a WPF Grid control that wraps the title TextBlock and a Listbox that contains the actual charts. The simplified XAML is:
<Grid x:Name="ReportPanel" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock />
<ListBox/>
</Grid>
With that UI established, lets jump to the code. When the user clicks the Print button, the following WPF command is executed:
this.PrintCommand = new SimpleCommand<Grid>
{
CanExecuteDelegate = execute => true,
ExecuteDelegate = grid =>
{
PrintCharts(grid);
}
};
This is pretty simple stuff. SimpleCommand implements the ICommand interface and lets me pass in some lambda expressions defining the code I want to run when this command is fired. Clearly, the magic happens in the PrintCharts(grid) call. The code shown below is basically the same code you would find in Pankaj’s article with a couple of modification highlighted in red.
private void PrintCharts(Grid grid)
{
PrintDialog print = new PrintDialog();
if (print.ShowDialog() == true)
{
PrintCapabilities capabilities = print.PrintQueue.GetPrintCapabilities(print.PrintTicket);
double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / grid.ActualWidth,
capabilities.PageImageableArea.ExtentHeight / grid.ActualHeight);
Transform oldTransform = grid.LayoutTransform;
grid.LayoutTransform = new ScaleTransform(scale, scale);
Size oldSize = new Size(grid.ActualWidth, grid.ActualHeight);
Size sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
grid.Measure(sz);
((UIElement)grid).Arrange(new Rect(new Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight),
sz));
print.PrintVisual(grid, "Print Results");
grid.LayoutTransform = oldTransform;
grid.Measure(oldSize);
((UIElement)grid).Arrange(new Rect(new Point(0, 0),
oldSize));
}
}
All right, what are these modifications? The most obvious is that I am replacing the use of the original this object (which represented the entire application window in the original code) with the Grid control that was passed in as part of the Command. So all of the measurements and transforms are executed using the Grid. The other change is that I have save the original Transform and Size of the Grid as well. The reason is that when you transform the Grid to fit to the printing page, it causes the actual application UI to change as well. This doesn't look so good on your screen, so after sending the Grid to the printer, I transform it back to its original screen layout.

Related

WPF - Creating a custom control with dynamic subcontrols

I am attempting to create a menu/navigation control in WPF that will be used across several applications. The control is intended to reside in a custom window, and will provide the maximize, minimize, close, drag, etc functionality. In addition to the standard "window" functions, the control will should also contain the main "menu" of the application - essentially a collection of buttons that each associate with a command and/or viewmodel - these buttons are custom controls as well (derived from radio buttons).
Essentially, my goal is to be able to add this menu control and it's buttons via XAML in a manner like this (this is pseudocode, to be clear):
<MenuControl Title="ApplicationTitle>
<MenuControl.MenuButtons>
<MenuButton Content="Button1" Command="Command1"/>
<MenuButton Content="Button2" Command="Command2"/>
</MenuControl.MenuButtons>
</MenuControl>
I've gotten to the point where I can get this working correctly for only ONE button. Once I add a second button, I get a "Specified argument was out of the range of the valid values" from my XAML.
Here is the code-behind related to the menu on my custom control:
private static readonly DependencyProperty MenuProperty = DependencyProperty.Register("Menu", typeof(ObservableCollection<NavigationButton>), typeof(CCTNavigationHeader), new FrameworkPropertyMetadata(new ObservableCollection<NavigationButton>()));
public ObservableCollection<NavigationButton> Menu
{
get
{
return (ObservableCollection<NavigationButton>)GetValue(MenuProperty);
}
set
{
SetValue(MenuProperty, value);
}
}
And here is the XAML:
<ItemsControl ItemsSource="{Binding ElementName=ctlCCTNavigationHeader, Path=Menu}"/>
This is the code utilizing the control that works, with only one button:
<Controls:CCTNavigationHeader Title="Test">
<Controls:CCTNavigationHeader.Menu>
<Controls:NavigationButton Content="Test"/>
</Controls:CCTNavigationHeader.Menu>
</Controls:CCTNavigationHeader>
And this is the code using the control that chokes, once I add a second button:
<Controls:CCTNavigationHeader Title="Test">
<Controls:CCTNavigationHeader.Menu>
<Controls:NavigationButton Content="Test"/>
<Controls:NavigationButton Content="Test"/>
</Controls:CCTNavigationHeader.Menu>
</Controls:CCTNavigationHeader>
I know I must be doing something incorrectly here, but I haven't been able to find any examples of accomplishing this type of solution anywhere. Can anyone familiar with creating custom user controls in WPF point me in the right direction?
Figured this one out. I wasn't initializing the Menu collection when creating the control. The following code fixed it:
public CCTNavigationHeader()
{
InitializeComponent();
Menu = new ObservableCollection<NavigationButton>(); //this line
}

How to Update UserControl (Chart) in MVVM

I have a chart embedded in a usercontrol
PlotControl.xaml (PlotControl.xaml.cs)
This plot control is used on a View in MVVM.
NOW! when I update the UI of PlotControl (e.g. I draw vertical and horizontal markers on chart), these updates are not visible on View (unless I do double click on the View or do a Minimize-Maximize window).
Is there a way the updated UI is updated automatically on View?
View code looks like:
<Grid Margin="4">
<nms:PlotControl x:Name="PlotControl" Margin="10,10" DockPanel.Dock="Right" />
</nms:PlotControl>
Snippet from PlotControl.xaml.cs code looks like:
ChartPanel cpnl = new ChartPanel();
chart.View.Layers.Add(cpnl);
ChartMarker rightVerticalMarker = new ChartMarker(chart, MarketType.RightVertical);
rightVerticalMarker.DataPoint = new Point(30, double.NaN);
cpnl.Children.Add(rightVerticalMarker);
cpnl.UpdateLayout();
chart.UpdateLayout();
ChartMarker is simply a line Horizontal (or Vertical) defined by enum MarketType.
NOTE: I have been searching for this problem since two days in SO, but nowhere could I find the solution.
Updates can be forced by Arrange methods. Here is what I have found, which solved my problem.
Size userControlSize = this.RenderSize; //original size of userControl
Size chartSize = chart.RenderSize; // original size of chart
chart.Arrange(chart.View.PlotRect); // forcing layout elements on chart
chart.Arrange(new Rect(chartSize)); // setting the chart size to original value
Arrange(new Rect(userControlSize)); // setting the userControl size to original value

WPF hit testing a rectangular area

I have a WrapPanel containing an arbitrary number of jagged sized elements. I'd like to implement drag select for my items.
It seems pretty obvious how to HitTest for a point, but how can I find all items within a rectangular area?
You may use VisualTreeHelper.HitTest with a GeometryHitTestParameters argument and a HitTestFilterCallback that checks if a Visual is a direct child of the Panel.
Something like this:
var selectedElements = new List<DependencyObject>();
var rect = new RectangleGeometry(...);
var hitTestParams = new GeometryHitTestParameters(rect);
var resultCallback = new HitTestResultCallback(
result => HitTestResultBehavior.Continue);
var filterCallback = new HitTestFilterCallback(
element =>
{
if (VisualTreeHelper.GetParent(element) == panel)
{
selectedElements.Add(element);
}
return HitTestFilterBehavior.Continue;
});
VisualTreeHelper.HitTest(
panel, filterCallback, resultCallback, hitTestParams);
It looks a little complicated, but the HitTestFilterCallback is necessary to get all Visuals in the visual tree, not only those that actually got hit. For example if your panel contains Label controls, the HitTestResultCallback will only be called for the Border and TextBlock child Visuals of each Label.
The option for controlling hit test visibility is the IsHitTestVisible property. This property allows you to control hit test visibility regardless of the brush with which the UIElement is rendered.
Also, You want to set the Fill to Transperent
<Rectangle Width="200" Height="200" Margin="170,23,12,35" Fill="Transparent" IsHitTestVisible="True" />

WPF ContextMenu placement adjusted event

Does anyone know of how I can determine when the ContextMenu get its placement automatically adjusted due to being too close to the edge of the screen?
My scenario is that I have a ContextMenu that has 2 rounded corners and 2 square corners. When the menu opens down I round the bottom 2, and if the menu is opening upwards then I round the top 2. The problem is that I haven't found an event or property to bind to that tells me when the menu gets its direction automatically changed.
Here's some simplified sample code to try out. If you click when the window is at top of screen then menu goes down. If you move window to bottom of screen then the menu will go up.
<Window x:Class="menuRedirection.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="100" Width="200">
<DockPanel Name="panel" ContextMenuOpening="DockPanel_ContextMenuOpening">
<DockPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="item"/>
<MenuItem Header="item"/>
<MenuItem Header="item"/>
<MenuItem Header="item"/>
</ContextMenu>
</DockPanel.ContextMenu>
<Rectangle DockPanel.Dock="Bottom" Name="menuTarget" Fill="Red" Height="10"/>
<TextBlock DockPanel.Dock="Top" Text="right click for context menu"/>
</DockPanel>
</Window>
private void DockPanel_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
ContextMenuService.SetPlacement(panel, PlacementMode.Bottom);
ContextMenuService.SetPlacementTarget(panel, menuTarget);
}
Here's what the real application looks like so you can see my problem with needing to know to adjust my rounded corners.
As far as I can tell, this is not possible.
Using JustDecompile, I traced this functionality to the UpdatePosition method in the Popup class. The final location seems to be set here:
this._positionInfo.X = num4;
this._positionInfo.Y = num5;
this._secHelper.SetPopupPos(true, num4, num5, false, 0, 0);
_secHelper is a helper class of type PopupSecurityHelper, and seems to just be an internal helper...And, none of these result in an event or even a public property being changed.
Here is an MSDN article explaining how popup positioning is determined in general (The 'When the popup encounters the edge of a screen' describes your scenario).
However, this article explains how you can use the CustomPopupPlacementCallback to override these behaviors somewhat. However, this still uses a PopupPrimaryAxis, which should flip the menu as necessary, and will result in the same problem.
The only other thing I could think of is that you could look into the PlacementRectangle and maybe poll the size and location similar to how UpdatePosition does things...or just check the popup itself just like UpdatePosition does.
This is a private method, though. So, any logic you try to mimic could change in a future version of the framework.
UPDATE
Also, you could possibly try bastardizing PointToScreen or PointFromScreen, but that would be very convoluted code if it worked...
I was unable to find a true WPF solution but Justin's comment lead me down the path of experimenting with comparing the menu's location with the PlacementTarget's location.
First step was to subscribe to the contextMenu.Loaded event (this fires after layout has been processed but before it's fully visible on the screen).
<ContextMenu ContextMenu.Loaded="ContextMenu_Loaded">
And then when that fires I can figure out if the menu was internally switched to the alternate placement for my requested placementMode. If it was reversed then I go ahead and adjust my rounded corners accordingly.
NOTE: i initially had used getWindowRect and compared the menu Rect with the target's Rect, but found that the menu Rect was always returning the prior instance's location. To avoid this problem I now get the relevant screen's workingArea and manually see if the menu fits.
NOTE2: be sure your menu's template results in the same window height for both inverted and regular display. Otherwise, your calculation could be off since getWindowRect returns the last menu's size.
void ContextMenu_Loaded(object sender, RoutedEventArgs e)
{
bool reversed = isMenuDirectionReversed(this.ContextMenu);
//existing styles are read-only so we have to make a clone to change a property
if (reversed)
{//round the top corners if the menu is travelling upward
Style newStyle = new Style(typeof(ContextMenu), this.ContextMenu.Style);
newStyle.Setters.Add(new Setter { Property = Border.CornerRadiusProperty, Value = new CornerRadius(10, 10, 0, 0) });
this.ContextMenu.Style = newStyle;
}
else
{ //since we may have overwritten the style in a previous evaluation,
//we also need to set the downward corners again
Style newStyle = new Style(typeof(ContextMenu), this.ContextMenu.Style);
newStyle.Setters.Add(new Setter { Property = Border.CornerRadiusProperty, Value = new CornerRadius(0, 0, 10, 10) });
this.ContextMenu.Style = newStyle;
}
}
Evaluation method:
private bool isMenuDirectionReversed(ContextMenu menu)
{
//get the window handles for the popup' placement target
IntPtr targetHwnd = (HwndSource.FromVisual(menu.PlacementTarget) as HwndSource).Handle;
//get the relevant screen
winFormsScreen screen = winFormsScreen.FromHandle(targetHwnd);
//get the actual point on screen (workingarea not taken into account)
FrameworkElement targetCtrl = menu.PlacementTarget as FrameworkElement;
Point targetLoc = targetCtrl.PointToScreen(new Point(0, 0));
//compute the location for the bottom of the target control
double targetBottom = targetLoc.Y + targetCtrl.ActualHeight;
if (menu.Placement != PlacementMode.Bottom)
throw new NotImplementedException("you need to implement your own logic for other modes");
return screen.WorkingArea.Bottom < targetBottom + menu.ActualHeight;
}
Final result:

Silverlight Measure Method not working properly depending of databound object?

UPDATE II
Problem was solved. Thank you.
For a simple Silverlight printing preview engine, my XAML looks like this (excerpt):
<Grid>
<TextBlock Text="{Binding IntroText}" />
<ItemsControl ItemsSource="{Binding DataItems}"
x:Name="DataItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
TextWrapping="Wrap"
Margin="0,2" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="{Binding OutroText}" />
</Grid>
I want to ensure that everything fits on a page, therefore i have a simple method:
public bool FitsOnPrintPage(Size pageDimensions)
{
Measure(new Size(pageDimensions.Width, Double.PositiveInfinity));
return
DesiredSize.Height <= pageDimensions.Height &&
DesiredSize.Width <= pageDimensions.Width;
}
Now we have a strange problem here which I can't explain:
The bound collection DataItems is a generic object List. When containing simple strings, the Measure(...) method works as expected and returns a properly calculated DesiredSize. So far, everything is working.
However, when having a simple object like this...
public class DataItem
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
...and changing the TextBlock Binding to <TextBlock Text="{Binding Path=Value1}"... the resulting view is identical, however the Measure(...) method doesn't return the expected values, the height of the Items is always zero. Also not working: keep Text Binding and override DataItems ToString() method. View working, Measure doesn't.
I was then trying to force a recalculation using methods like InvalidateMeasure() or UpdateLayout() on the DataTemplate or the whole page, without success.
Can you explain this?
UPDATE
Interesting: I've attached a simple custom ValueConverter to the TextBlock's Binding just for debugging reasons. When a string object is bound, I can see that Measure(...) is triggering the Binding - it's resolved first (i can see the debugger stepping into the ValueConverter) and measured afterwards. But when binding a custom class as described above, Measure(...) doesn't touch the Binding, i am stepping into the ValueConverters breakpoint "later". (Have to find out, when exactly)
Does this help you in any kind?
The answer is simple.. you working not in the 'silverlight way'
In Silverligth - it dosen't mather if string fits to the screen width or not, if string dosen't fit, just set TextBlock.Wrap to Wrap...
You have problem with this becose of 'old way of thinking'...
But if you want it so much try this:
var ContainerGrid = new Grid(); // create grid at runtime
// !!! it's important for controlToMesure.Parent property to be NULL, if it's not
// !!! then temporary remove controlToMesure from parent container...
ContainerGrid.Children.Add(controlToMesure); // add control that you want to mesure
ContainerGrid.Measure(new Size(pageWidth, pageHeight));
ContainerGrid.Arrange(new Rect(0, 0, pageWidth, pageHeight));
ContainerGrid.UpdateLayout();
var size = ((FrameworkElement)ContainerGrid.Children[0]).DesiredSize;
Here is the code from http://silverpdf.codeplex.com/
Maybee it would help you, but you have to modify it, to make it usable.
private System.Windows.Size CalculeteSize()
{
var s = new System.Windows.Controls.StackPanel()
{
VerticalAlignment = System.Windows.VerticalAlignment.Center,
HorizontalAlignment = System.Windows.HorizontalAlignment.Center
};
var fs = FontPool.GetFontStream(Typeface.FontFamily.Source);
s.Children.Add(new System.Windows.Controls.TextBlock
{
Text = Text,
FontSource = new FontSource(fs),
FontSize = EmSize,
FontFamily = Typeface.FontFamily,
FontStretch = Typeface.FontStretch,
FontStyle = Typeface.FontStyle,
FontWeight = Typeface.FontWeight,
});
s.Measure(new System.Windows.Size(double.MaxValue, double.MaxValue));
var aw = s.DesiredSize.Width;
var ah = s.DesiredSize.Height;
var size = new System.Windows.Size(aw, ah);
return size;
}
Solved
the problem was that the page controls were created and calculated first, and added to the displaying control after generation, because i wanted to avoid frequent UI updates. Something similar was even suggested by Ai_boy, who was trying to solve the problem by using an independent Grid Control - unfortunately this turned out as a misleading approach. Only after the generated page control was added to the visual tree, it automatically resolves the Bindings resulting in a proper size measuring.
Hope this helps anyone.

Resources