WPF sending mouse wheel event from ContextMenu to Window - c#

I have a window, on it, i have a button. The button has a context menu:
<Window>
<ScrollViewer Height="500">
<Button Height = "2000">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Item1"></MenuItem>
<MenuItem Header="Item2"></MenuItem>
<MenuItem Header="Item3"></MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</ScrollViewer>
</Window>
Whenever i right click on button, the context menu will be shown. When i move the mouse out of the context menu and scroll the wheel, the scrollViewer doesn't scroll at all. I have tried many ways on mouse leave or mouse enter events but nothing helps. I want the context menu is still showing but the wheel event is sent to the scrollViewer (or window), if i click outside of the contextmenu, it will close normally.
In win-form application, i have the same issue but i can solve it by using ContextMenuStrip as a replacement for ContextMenu. In WPF, looks like no ContextMenuStrip.

Unfortunately, context menu popups are not ancestors to their hosts when it comes to the WPF logical tree. This means that event bubbling doesn't work from context menu to the scrollviewer, so the scrollviewer won't be informed of any mouse wheel activity.
However, if you just want scrolling up/down in a scrollviewer to work, you can replicate the behavior yourself pretty easily by listening to the MouseWheel event on the context menu and then scrolling the ScrollViewer manually.
eg. the xaml:
<ScrollViewer Height="500" x:Name="MyScrollViewer">
<Button Height = "2000">
<Button.ContextMenu>
<ContextMenu x:Name="MyContextMenu">
<MenuItem Header="Item1"></MenuItem>
<MenuItem Header="Item2"></MenuItem>
<MenuItem Header="Item3"></MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</ScrollViewer>
In the codebehind for your view you could do something similar to the following:
public MainWindow()
{
InitializeComponent();
this.MyContextMenu.MouseWheel += OnContextMenuMouseWheel;
}
private void OnContextMenuMouseWheel(object sender, MouseWheelEventArgs e)
{
var currentOffset = MyScrollViewer.VerticalOffset;
var newOffset = currentOffset - e.Delta;
MyScrollViewer.ScrollToVerticalOffset(newOffset);
e.Handled = true;
}

Instread of using a ContextMenu, you could use a Popup that looks like a ContextMenu:
<ScrollViewer Height="500">
<Button Height="2000">
<Popup x:Name="popup" Placement="Mouse" StaysOpen="False"
xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero2">
<theme:SystemDropShadowChrome Name="Shdw" Color="Transparent" SnapsToDevicePixels="true">
<Border Name="ContextMenuBorder" Background="#F5F5F5" BorderBrush="#FF959595" BorderThickness="1" SnapsToDevicePixels="True">
<ScrollViewer Name="ContextMenuScrollViewer" Grid.ColumnSpan="2" Margin="1,0"
Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=MenuScrollViewer}}">
<Grid RenderOptions.ClearTypeHint="Enabled">
<Canvas Height="0" Width="0" HorizontalAlignment="Left" VerticalAlignment="Top">
<Rectangle Name="OpaqueRect" Height="{Binding ElementName=ContextMenuBorder, Path=ActualHeight}"
Width="{Binding ElementName=ContextMenuBorder, Path=ActualWidth}"
Fill="{Binding ElementName=ContextMenuBorder, Path=Background}"
SnapsToDevicePixels="True"/>
</Canvas>
<Rectangle Fill="#F1F1F1" HorizontalAlignment="Left" Width="28" Margin="1,2" RadiusX="2" RadiusY="2" SnapsToDevicePixels="True"/>
<Rectangle HorizontalAlignment="Left" Width="1" Margin="29,2,0,2" Fill="#E2E3E3" SnapsToDevicePixels="True"/>
<Rectangle HorizontalAlignment="Left" Width="1" Margin="30,2,0,2" Fill="White" SnapsToDevicePixels="True"/>
<StackPanel>
<MenuItem Header="Item1"></MenuItem>
<MenuItem Header="Item2"></MenuItem>
<MenuItem Header="Item3"></MenuItem>
</StackPanel>
</Grid>
</ScrollViewer>
</Border>
</theme:SystemDropShadowChrome>
</Popup>
<Button.Triggers>
<EventTrigger RoutedEvent="MouseRightButtonUp">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="popup" Storyboard.TargetProperty="IsOpen">
<DiscreteBooleanKeyFrame KeyTime="00:00:00.1" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</ScrollViewer>
Remember to add a reference to PresentationFramework.Aero2.dll if you want to use SystemDropShadowChrome.

Related

How do i get a Menu in the Window bar?

I would like to know how to get a menu in the window bar, the same as Visual studio does.
It would be good to be able to have File, Edit, etc on the left and the standard Minimize, Maximize and Close buttons on the right. Is this at all possible?
I have tried setting Window WindowStyle="None" and adding my own icons in the bar but it doesnt seem right but is this the only way?
This is what i have at the moment.
<Window
Title="MainWindow"
Height="{x:Static SystemParameters.PrimaryScreenHeight}"
Width="{x:Static SystemParameters.PrimaryScreenWidth}"
Closing="Window_Closing"
WindowState="Maximized">
You must create your custom window chrome using the WindowChrome class:
The Window element in WPF is actually hosted in a non-WPF (non-client) host. This host includes the title bar (caption) and the standard buttons, an icon and the actual frame. This is known as window chrome.
Usually you can only modify the client area of a Window. But whith the help of the WindowChrome class, WPF allows the client area to extend into the non-client area.
The disadvantage is that you would have to basically replicate the original non-client area in order to preserve the original look and feel. But after all you still get some basic behavior like maximizing the window on double click ou of the box.
The following example is very basic, but should give you an idea how to achieve your task:
I highly recommend to follow the provided link to the WindowChrome class and read the remarks section, which contains very valuable information.
You can use the actual system values provided by the static SystemParameters class to get the current dimension values e.g. SystemParameters.WindowResizeBorderThickness which you should use in your custom chrome style.
Also note that to allow WPF to capture mouse events on you custom chrome's input elements like buttons or menus you must set the attached property WindowChrome.IsHitTestVisibleInChrome on each relevant element to true:
WindowChrome.IsHitTestVisibleInChrome="True"
The very basic style that creates the above visual is as followed:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework">
<Style x:Key="WindowStyle" TargetType="{x:Type Window}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome NonClientFrameEdges="Right"
ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid Background="Transparent">
<AdornerDecorator>
<Border Background="Transparent" Margin="{x:Static SystemParameters.WindowNonClientFrameThickness}">
<ContentPresenter />
</Border>
</AdornerDecorator>
<ResizeGrip x:Name="WindowResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Visibility="Collapsed"
IsTabStop="false" />
<Grid Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
Background="#FF3F3F3F"
VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Custom window chrome -->
<StackPanel Grid.Column="0" Orientation="Horizontal"
Margin="{x:Static SystemParameters.WindowResizeBorderThickness}">
<Image Source="{TemplateBinding Icon}" />
<TextBlock Text="{TemplateBinding Title}"
TextTrimming="CharacterEllipsis"
VerticalAlignment="Center"
Margin="16,0" />
<Menu shell:WindowChrome.IsHitTestVisibleInChrome="True">
<MenuItem Header="CustomChromeMenu">
<MenuItem Header="Action" />
</MenuItem>
</Menu>
</StackPanel>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Width="45"
Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
ToolTip="Minimize window"
ToolTipService.ShowDuration="5000"
shell:WindowChrome.IsHitTestVisibleInChrome="True">
<TextBlock Foreground="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
FontFamily="Segoe MDL2 Assets"
FontSize="11"
Text="" />
</Button>
<Button ToolTip="Maximize window"
Width="45"
Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
ToolTipService.ShowDuration="5000"
shell:WindowChrome.IsHitTestVisibleInChrome="True">
<TextBlock Foreground="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
FontFamily="Segoe MDL2 Assets"
FontSize="11"
Text="" />
</Button>
<Button ToolTip="Close application"
ToolTipService.ShowDuration="5000"
Width="50"
Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
shell:WindowChrome.IsHitTestVisibleInChrome="True">
<TextBlock Foreground="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
FontFamily="Segoe MDL2 Assets"
FontSize="11"
Text="" />
</Button>
</StackPanel>
</Grid>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="ResizeMode"
Value="CanResizeWithGrip">
<Setter TargetName="WindowResizeGrip"
Property="Visibility"
Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
The title bar contains
An icon
A title
A minimize button
A maximize button
A close button
And that's all. You can't add anything.
In VS, the title bar is masked. The "title bar" you see is in fact the content of the window. That is, as you seemed to suspect, the only way.
That's also why tooltips on the three right buttons are in the OS language for most apps (because the title bar is managed by the system), but are in the app language for Visual Studio.
You have to set WindowStyle to None to mask the real title bar.
Then, inside your window, you should add a DockPanel and dock to the top an image and a menu on the left, and 3 buttons on the right.
The minimize button should change the WindowState to Minimized.
The maximize button should change the WindowState to either Normal or Maximized and its icon should be based on the WindowState.
The close button should call the Close() method and/or the Application.Current.Shutdown(0) method.
You should also subscribe to events like MouseLeftButtonDown, MouseLeftButtonUp and MouseMove to move the window.

How to set small expander header and widen the content in WPF?

I'm creating a custom virtual keyboard. Currently, for all punctuation, I set it on another Grid. Expander content seems can hold the punctuation view. But currently it need to set the size of the content if I want the expander content to be able to view the content inside.
How do I make to be able to have button size expander and when click, the content will be expanding to it's wider size?
How to make expander are size as below image (yellow rectangle) but when click on the expander, the content will be expand like GIF?
<Button HorizontalAlignment="Left" Height="52.25" Margin="0,238.5,0,0" VerticalAlignment="Top" Width="168.187" Background="#FF3C3C3C" FontFamily="Arial" FontSize="18" BorderBrush="#FF6E6E6E">
<Expander Header="?!#" ExpandDirection="Up" Background="{x:Null}" Foreground="White" HorizontalAlignment="Left" VerticalAlignment="Top">
<!-- How add content grid which can be expander at full while maintaining the expander header to small size? -->
</Expander>
</Button>
I suggest you to put the Buttons of the extended Keyboard in another Panel and control the Visibility of the Panel with a ToggleButton.
When you use an Expander (as you did) to hold the extended Keyboard this can be easily done similar to this:
<Grid Background="Gray" Height="300">
<!-- Basic Keyboard Buttons can be placed here -->
<Button HorizontalAlignment="Left" Margin="0,0,0,30" VerticalAlignment="Top" Content="Put some Buttons from the BASIC Keyboard here"/>
<!-- Button to bring the extanded Keyboard into view -->
<ToggleButton x:Name="ExtendedKeyboardActive" HorizontalAlignment="Left" VerticalAlignment="Center" Content="?!#" Width="50"/>
<!-- Extended Keyboard (Note: I would rather use a simple <Grid/> instead of an <Expander/> but it is up to you)-->
<Expander VerticalAlignment="Bottom" ExpandDirection="Up" IsExpanded="{Binding IsChecked, ElementName=ExtendedKeyboardActive}" Background="LightGray">
<Grid Height="300">
<!-- Content of the extaned Keyboard -->
<Button HorizontalAlignment="Left" Margin="0,0,0,30" VerticalAlignment="Top" Content="Put some Buttons from the EXTENDED Keyboard here"/>
<!-- Button ti hide the extended Keyboard (optional if it the 'ExtendedKeyboardActive' is not covered over by the extended Keyboard Grid) -->
<ToggleButton IsChecked="{Binding IsChecked, ElementName=ExtendedKeyboardActive}" Width="50" HorizontalAlignment="Left" VerticalAlignment="Center" Content="ABC" />
</Grid>
</Expander>
</Grid>
Or you could use a PopUp, because the Expander has this Arrow-Circle-Thing which is always displayed and not really needed (Thanks #Bradley Uffner).
<Grid Background="Gray">
<!-- Basic Keyboard Buttons can be placed here -->
<Button HorizontalAlignment="Left" Margin="0,0,0,30" VerticalAlignment="Bottom" Content="Put some Buttons from the BASIC Keyboard here"/>
<!-- Button to bring the extanded Keyboard into view -->
<ToggleButton x:Name="ExtendedKeyboardActive" HorizontalAlignment="Left" VerticalAlignment="Bottom" Content="?!#" Width="50"/>
<!-- Extended Keyboard -->
<Popup IsOpen="{Binding IsChecked, ElementName=ExtendedKeyboardActive}" Placement="Center" Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}">
<Grid Background="LightGray">
<!-- Content of the extended Keyboard -->
<Button HorizontalAlignment="Left" Margin="0,0,0,30" VerticalAlignment="Bottom" Content="Put some Buttons from the extended Keyboard here"/>
<!-- Button to go back to the basic Keyboard -->
<ToggleButton IsChecked="{Binding IsChecked, ElementName=ExtendedKeyboardActive}" Width="50" HorizontalAlignment="Left" VerticalAlignment="Bottom" Content="ABC" />
</Grid>
</Popup>
</Grid>

WPF Buttons with Paths as Content

I ran into an interesting thing and I can't figure out how to fix it at the moment, maybe I'm missing something really simple.
I'm re-designing some of the UI but I'm not really experienced with it right now.
So I have 3 buttons and I wanted to draw patterns as the content of the buttons instead of using images.
I have managed to do so(still needs tweaking), but the buttons on hover event only triggers once my pointer enters the pattern area instead of the area of the button. Does anyone mind taking a look at it please?
<Button Grid.Column="0" Width="35" Height="35"
Command="{StaticResource MinimizeCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Background="{x:Null}" BorderBrush="{x:Null}">
<Path Data="M20,14H4V10H20" VerticalAlignment="Center"
HorizontalAlignment="Center" Height="2.6"
Margin="7,20,7,10" Stretch="Fill" Width="16">
<Path.Fill>
<SolidColorBrush Color="{DynamicResource MainForeColor}"/>
</Path.Fill>
</Path>
</Button>
Code : https://hastebin.com/kofaqehoti.xml
By setting the background and the border brushes of the buttons to null the button will only be clickable and detectable for hover where the paths are drawn. Replace the nulls with Transparent or #00000000 and the buttons will be invisible but clickable/hoverable (If that's a word)
<Button Grid.Column="0" Width="35" Height="35"
Command="{StaticResource MinimizeCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Background="Transparent" BorderBrush="#00000000">
<Path Data="M20,14H4V10H20" VerticalAlignment="Center" HorizontalAlignment="Center" Height="2.6" Margin="7,20,7,10" Stretch="Fill" Width="16">
<Path.Fill>
<SolidColorBrush Color="{DynamicResource MainForeColor}"/>
</Path.Fill>
</Path>
</Button>

Access DataTemplate controls in code behind

I have problem with this code:
<ListBox x:Name="lbInvoice" ItemsSource="{Binding ocItemsinInvoice}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ToggleButton x:Name="btnInvoiceItem">
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="btnInvoiceQuantity" Content="{Binding Quantity}"/>
<TextBlock Text="{Binding Item.ItemName}" Width="175" Padding="7,5,0,0"/>
</StackPanel>
</ToggleButton>
<Popup x:Name="popQuantity" Closed="popQuantity_Closed" PlacementTarget="{Binding ElementName=btnInvoiceQuantity}" IsOpen="{Binding IsChecked,ElementName=btnInvoiceQuantity}">
<Grid>
<TextBlock x:Name="tbUnitPrice" Text="Unit Price"/>
<Button x:Name="btnClosePopup" Click="btnClosePopup_Click">
</Grid>
</Popup>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
In code behind in btnClosePopup click event I can't access to popup to close it and do some other changes on it.
I have tried to use FindName() method but it doesn't work for me
var template = lbInvoice.Template;
var myControl = (Popup)template.FindName("popQuantity", lbInvoice);
Please can you help and tell me how do I access to controls that inside DataTemplate in code behind?
You don't have to do it in code behind and if you change Popup.IsOpen in code it won't appear again as you'll lose you binding. You need to set IsChecked on ToggleButton to false and you can do it with EventTrigger
<Button Content="Close" x:Name="btnClosePopup">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName=" btnInvoiceQuantity" Storyboard.TargetProperty="IsChecked">
<DiscreteBooleanKeyFrame Value="False" KeyTime="0:0:0"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
You have already to Open/Close this Popup in this line:
IsOpen="{Binding IsChecked, ElementName=btnInvoiceQuantity}"
As an alternative answer from #dkozl, you can close the Popup in such a way:
<Popup x:Name="popQuantity"
IsOpen="{Binding Path=IsChecked, ElementName=btnInvoiceQuantity}">
<Grid Width="200" Height="200" Background="Gainsboro">
<TextBlock Text="Unit Price" />
<ToggleButton x:Name="btnClosePopup"
IsChecked="{Binding Path=IsChecked, ElementName=btnInvoiceQuantity}"
Content="Close"
Width="100"
Height="30" />
</Grid>
</Popup>
Or you can directly specify a property IsOpen of Popup:
<ToggleButton x:Name="btnClosePopup"
IsChecked="{Binding Path=IsOpen, ElementName=popQuantity}" ... />
But in this case at the background color of Button will be in state of IsChecked="True". To avoid this, without creating a new Template for your Control, you can use a system style of flat button:
<ToggleButton x:Name="btnClosePopup"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}" ... />

WPF Window - Fade Different parts of the same window

I have a basic WPF windows with the markup as specific below:
<Window x:Class="Application.SomeWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SomeWindow"
Topmost="True" WindowStyle="None" Height="39" Width="400"
ResizeMode="NoResize" ShowInTaskbar="False"
WindowStartupLocation="Manual" Background="Transparent"
Closing="Window_Closing" AllowsTransparency="True" Opacity="0">
<Border Background="CornflowerBlue" BorderBrush="Black" BorderThickness="0,0,0,0" CornerRadius="5,5,5,5" Opacity="0.75">
<Grid>
<!-- Display bar -->
<Image Grid.Row="1" Height="24" Margin="7,7,0,0" Name="img1" Stretch="Fill" VerticalAlignment="Top" Source="/Application;component/Images/dashboard/1.png" HorizontalAlignment="Left" Width="13" />
<Image Height="24" Margin="19,7,47,0" Name="image21" Source="/Application;component/Images/dashboard/2.png" Stretch="Fill" VerticalAlignment="Top" Grid.Row="1" Grid.ColumnSpan="2" />
<!-- Button 1 -->
<Button Style="{DynamicResource NoChromeButton}" Height="27" Margin="0,5,25,0" Name="btn1" Click="btn1_Click" VerticalAlignment="Top" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" Width="23" ToolTip="1">
<Image Height="26" HorizontalAlignment="Right" Name="img1" Source="/Application;component/Images/dashboard/3.png" VerticalAlignment="Top" Width="22" Stretch="Fill" />
</Button>
<!-- Button 2 -->
<Button Style="{DynamicResource NoChromeButton}" Height="27" Margin="0,5,5,0" Name="btn2" Click="btn2_Click" VerticalAlignment="Top" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" Width="23" ToolTip="2">
<Image Height="26" HorizontalAlignment="Right" Name="img2" Source="/Application;component/Images/dashboard/4.png" VerticalAlignment="Top" Width="22" Stretch="Fill" />
</Button>
</Grid>
</Border>
</Window>
Here is what it looks like now:
What I'd really like to do is make it so that initially looks like this:
Then, once mouseover happens, to fade background opacity in from 0 so it looks like the first image. The problem is that if I set the Border or Grid Background color to Transparent with the goal of fading in on mouseover, then everything inside the Border or Grid is affected as well.
Is there a way to manage the opacities of window and its UI elements seperately? Or perhaps there is a totally different route to take to get this background fade on mouseover? Thanks.
There are two options. Number one is to just move the outer border inside the grid, as the first child (and have the other controls alongside it, not in it). That way it will fade by itself, but still be behind the other controls. You will of course either have to set ColumnSpan/RowSpan, or wrap the entire thing in another Grid.
The second option is to just fade the background, not the entire border:
<Border ...>
<Border.Background>
<SolidColorBrush Color="CornflowerBlue" Opacity="0.5"/>
</Border.Background>
...
try this trick - draw a rectangle or border with dimensions bind to parent or ElementName.
It won't affect rest of elements of tree. Works for me.
<Grid x:Name="abc">
<Border
Width="{Binding ActualWidth, ElementName=abc}"
Height="{Binding ActualHeight, ElementName=abc}"
Background="Blue"
Opacity="0.5"/>
//buttons or inner grid
...
</Grid>
If you don'w want to use ElementName, simply replace Width and Height by
Width="{Binding ActualWidth, Source={RelativeSource Mode=FindAncestor, AncestorType=Grid}}"
Height="{Binding ActualHeight, Source={RelativeSource Mode=FindAncestor, AncestorType=Grid}}"

Categories