WPF ContextMenu placement adjusted event - wpf

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:

Related

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" />

Popup to appear on the bottom-right corner of its parent

I'm trying to design a Popup which will appear on the bottom-right corner of its PlacementTarget
Let's admit that you set its PlacementTarget to a Window, well, the Popup will act as classic toaster notifications.
Given the fact that WPF is not smart enough to provide us a "corner" solution, I'm trying to implement a new control, inheriting from Popup , which will place itself at the appropriate location.
Here is my first idea: work on Loaded event to determine where should I place the Popup.
Problem? I don't want to give any fixed dimensions to the popup, which is supposed to size itself according to the text displayed.
However, I can't get the ActualWidth property when Loaded event is raised.
I can't have it either when Opened event is raised.
Here is the draft code so far:
public class ExceptionPopup : Popup
{
public ExceptionPopup()
{
InitializeComponent();
Loaded += new RoutedEventHandler(ExceptionPopup_Loaded);
}
void ExceptionPopup_Loaded(object sender, RoutedEventArgs e)
{
if (PlacementTarget != null)
{
if (PlacementTarget is FrameworkElement)
{
parentWidth = (PlacementTarget as FrameworkElement).ActualWidth;
parentHeight = (PlacementTarget as FrameworkElement).ActualHeight;
}
}
}
protected override void OnOpened(EventArgs e)
{
this.HorizontalOffset = parentWidth;
this.VerticalOffset = parentHeight;
base.OnOpened(e);
}
}
Is there any other event I could use to catch what I want here?
I'd basically like to set HorizontalOffset to parentWidth - ActualWidth/2 , same for height :)
Any idea?
Thanks!
Usually I set the PlacementTarget to either Bottom or Right, then apply a RenderTransform which shifts the Popup by the remaining value.
For example, I might use Placement=Bottom, then use a RenderTransform to shift the popup (Window.Width - Popup.Width) to the right, and Popup.Height upwards. You might not even need to re-adjust based on the Popup Height/Width becauase MSDN says that Popups are not allowed to be displayed off screen, and it will automatically adjust their placement to keep them visible
Be sure you use a RenderTransform instead of a LayoutTransform, because RenderTransforms get applied after the Popup gets Rendered, so the ActualHeight and ActualWidth will be greater than 0.

Canvas in ScrollViewer (Preview)MouseButtonDown event order

If we have
<ScrollViewer Name="scroll_viewer" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Canvas Name="canvas" Height="200" Width="200">
<Rectangle Fill="AliceBlue" Width="100" Height="100"/>
</Canvas>
</ScrollViewer>
with handlers for:
scroll_viewer.PreviewMouseLeftButtonDown
scroll_viewer.MouseLeftButtonDown
canvas.PreviewMouseLeftButtonDown
Then if we click in the Rectangle we get scroll_viewer_PreviewMouseLeftButtonDown called first then canvas_PreviewMouseLeftButtonDown but scroll_viewer_MouseLeftButtonDown is not called.
I want to handle the click event first in the canvas - if an object is clicked I want to handled the event (for object drag). If no canvas object is clicked I want to handle event in scroll_viewer (to manage scrollview panning with the mouse).
How to manage this given that the call order is the oposite of what i want and that the non perview version scroll_viewer.MouseLeftButtonDown is not called?
UPDATE:
From this post: Silverlight forums
((FrameworkElement)scroll_viewer.GetValue(ScrollViewer.ContentProperty)).MouseLeftButtonDown += scroll_viewer_MouseLeftButtonDown;
DOES work ie does get called after the preview events - can some explain why this less than obvious syntax is required?
The problem is that the ScrollViewer already handles the MouseLeftButtonDown event internally, like so:
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
if (base.Focus())
e.Handled = true;
base.OnMouseLeftButtonDown(e);
}
You can "fix" this using a custom class, like so:
public class MyScrollViewer : ScrollViewer {
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
e.Handled = false;
}
}
SIDE NOTE: You should use x:Name in XAML, not Name. Otherwise you may run into compilation errors using the above class.
Alternatively, you could attach your handler for all MouseLeftButtonDown events, including handled ones. So instead of:
this.scroll_viewer.MouseLeftButtonDown += new MouseButtonEventHandler(scroll_viewer_MouseLeftButtonDown);
You'd use:
this.scroll_viewer.AddHandler(ScrollViewer.MouseLeftButtonDownEvent, new MouseButtonEventHandler(this.scroll_viewer_MouseLeftButtonDown), true);
The Preview events follow a routing strategy similar to the Tunneling strategy, meaning that the event starts at the top of the element tree, and travels down it. So it would hit your ScrollViewer first, then your Canvas.
The non-Preview events follow a routing strategy similar to the Bubbling strategy, meaning that events start on the object they occurred on, and travel up the element tree. In this case, the Canvas would get hit first, then the ScrollViewer.
You can read more about the Routing strategies here
As a side note, for Canvas objects to be visible for HitTest events, they need to have a non-transparent background. So if you have a Canvas with no background color specified, it will default to Transparent and not be visible for HitTests.

RibbonControl: center Title

I'm using the Microsoft RibbonControl on a UserControl (it needs to be so we can host it on a stub form to host the WPF in our MDI system). Sadly, the title of the Ribbon displays Top/Left in the ribbon's header, and it looks ridiculous. How do I get at that sucker?
I am working on just about the same thing right now. I solved it by using a datatemplate for the ribbon title:
<r:Ribbon.TitleTemplate>
<DataTemplate>
<TextBlock Text="Put you title here" Margin="3,3,0,0"></TextBlock>
</DataTemplate>
</r:Ribbon.TitleTemplate>
If the ribbon is used in a RibbonWindow, you probably also want to add a glow to the title text to be able to read it properly when placed over a dark background. In that case, add this XAML inside the TextBlock:
<TextBlock.BitmapEffect>
<OuterGlowBitmapEffect GlowColor="White" Opacity="0.7" GlowSize="10"/>
</TextBlock.BitmapEffect>
There is one more problem with the Ribbon when used within a RibbonWindow; the title text will either be placed correctly when window state is Normal or when window is maximized. To solve this I bound the TextBlock Margin to a property in the codebind:
public Thickness TitleMargin
{
get { return this.WindowState == WindowState.Maximized ? new Thickness(0, 3, 0, 0) : new Thickness(0); }
}
To get this working, you also need to fire a PropertyChanged event each time the window state changes:
protected override void OnStateChanged(EventArgs e)
{
OnPropertyChanged("TitleMargin");
base.OnStateChanged(e);
}

Printing of WPF Window on one page

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.

Resources