WPF Popup positioning and drawn issues - wpf

I have a UserControl in which I create an Style that is applied later to the ContentControl of a Popup (below is all defined in the UserControl):
<Style x:Key="ttPopupContent" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<!-- BOTTOM Popup -->
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid x:Name="Grid">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle MinWidth="40" Fill="#fff" Stroke="#FF000000" Grid.Row="1" />
<StackPanel Grid.Row="1">
<TextBlock Text="My popup title for bottom popup" TextWrapping="Wrap"/>
<TextBlock Text="My popup body content for bottom popup" TextWrapping="Wrap" />
</StackPanel>
<Path Fill="#fff" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left"
Margin="{TemplateBinding Tag}" Width="20" Grid.Row="0" Data="M 0,21 L 10,0 20,21" />
<ContentPresenter Margin="8" Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Placement, ElementName=myPopup}" Value="Top">
<!-- TOP Popup -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid x:Name="Grid">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Rectangle MinWidth="40" Fill="#fff" Stroke="#FF000000" Grid.Row="0" />
<StackPanel Grid.Row="0">
<TextBlock Text="My popup title for top popup"
TextWrapping="Wrap"/>
<TextBlock Text="My popup body content for top popup"
TextWrapping="Wrap" />
</StackPanel>
<Path Fill="#fff" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left"
Margin="{TemplateBinding Tag}" Width="20" Grid.Row="1" Data="M 0,0 L 10,20 20,0" />
<ContentPresenter Margin="8" Grid.Row="0" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<!-- this label is placed at leftmost of the screen -->
<TextBlock x:Name="txtBlck1" Text="ShowInfo" />
<Popup x:Name="myPopup1" AllowsTransparency="True" Opened="Popup_Opened1"
PlacementTarget="txtBlck1" Placement="Bottom">
<ContentControl Style="{StaticResource ttPopupContent}"/>
</Popup>
<!-- this label is placed at rightmost of the screen -->
<TextBlock x:Name="txtBlck2" Text="AnotherLabel" />
<Popup x:Name="myPopup2" AllowsTransparency="True" Opened="Popup_Opened2"
PlacementTarget="txtBlck2" Placement="Bottom">
<ContentControl Style="{StaticResource ttPopupContent}"/>
</Popup>
Sometimes it does not put the arrow pointing correctly to the label at which popup is bound. For example, if I hover on the mouse on label "AnotherLabel" it is drawn as follows (this label is at the rightmost of the screen):
as you can see the arrow is not placed in the right place. However, I have another label "ShowInfo" that is placed at the leftmost of the screen, then it works:
So I am trying to adjust the arrow horizontal alignment to point correctly to the label by doing this in code-behind (xaml.cs):
private void Popup_Opened1(object sender, EventArgs e)
{
UIElement target = myPopup1.PlacementTarget;
Point adjust = target.TranslatePoint(new Point(8, 0), popup);
if (adjust.Y > 0)
{
popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Top;
}
myPopup1.Tag = new Thickness(adjust.X, -1.5, 0, -1.5);
}
private void Popup_Opened2(object sender, EventArgs e)
{
UIElement target = myPopup2.PlacementTarget;
Point adjust = target.TranslatePoint(new Point(8, 0), popup);
if (adjust.Y > 0)
{
popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Top;
}
myPopup2.Tag = new Thickness(adjust.X, -1.5, 0, -1.5);
}
What I am trying to do in code-behind is put the arrow in the correct place by adjusting it horizontally as explained here (in that case is a tooltip, in my case is a Popup).
I have two problems:
Adjusting horizontally the arrow to point correctly to the label.
The arrow is not drawn correctly, it appears a black line under. See below image:

You should extend Popup to conveniently implement the desired behavior.
I suggest to internally set the Popup.Placement to PlacementMode.Custom to enable custom positioning.
This gives you easy access to the positioning context in order to detect when the placement target is positioned right on the screen (in other words the Popup is right aligned with the parent Window.
The arrow box and the tool tip content should be hosted by a custom control.
To draw the arrow box properly you should create and draw geometry for example by overriding UIElement.OnRender of the custom control.
For simplicity, the following example only supports bottom right placement. You can follow the simple pattern to extend the features to support additional positioning (read comments in code):
public class MyToolTip : Popup
{
internal enum Side
{
None = 0,
Left,
Top,
Right,
Bottom
}
private MyToolTipContent ToolTipContent { get; }
static MyToolTip()
{
PlacementTargetProperty.OverrideMetadata(typeof(MyToolTip), new FrameworkPropertyMetadata(default(object), OnPlacementTargetChanged));
PlacementProperty.OverrideMetadata(typeof(MyToolTip), new FrameworkPropertyMetadata(null, CoercePlacement));
}
public MyToolTip()
{
this.ToolTipContent = new MyToolTipContent();
this.Child = this.ToolTipContent;
// Enable custom placement to get context related placement info
// like size and positions.
this.CustomPopupPlacementCallback = CalculatePosition;
this.Placement = PlacementMode.Custom;
this.AllowsTransparency = true;
}
private static void OnPlacementTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as MyToolTip).OnPlacementTargetChanged(e.OldValue as UIElement, e.NewValue as UIElement);
private static object CoercePlacement(DependencyObject d, object baseValue)
{
// Backup/delegate original PlacementMode as we need to override it
// in order to enable custom placement (and to keep it enabled by force).
(d as MyToolTip).ToolTipContent.Placement = (PlacementMode)baseValue;
// Enforce custom placement (invokation of the Popup.CustomPopupPlacementCallback)
return PlacementMode.Custom;
}
// Show Popup on mouse hover over the placement target
protected virtual void OnPlacementTargetChanged(UIElement oldTarget, UIElement newTarget)
{
if (oldTarget is not null)
{
newTarget.MouseEnter -= OnTargetMouseEnter;
newTarget.MouseLeave -= OnTargetMouseLeave;
}
if (newTarget is not null)
{
newTarget.MouseEnter += OnTargetMouseEnter;
newTarget.MouseLeave += OnTargetMouseLeave;
}
}
private void OnTargetMouseLeave(object sender, MouseEventArgs e) => this.IsOpen = false;
private void OnTargetMouseEnter(object sender, MouseEventArgs e) => this.IsOpen = true;
private CustomPopupPlacement[] CalculatePosition(Size popupSize, Size targetSize, Point offset)
{
Side clippingSide = GetParentWindowClippingSide(targetSize);
// TODO::Handle clipping of remaining sides (top and bottom. Left side will always fit).
switch (clippingSide)
{
// Popup will clip right Window bounds => shift Popup to the left to compensate
// (i.e. right align with parent Window).
case Side.Right:
AlignRightSide(popupSize, ref offset, targetSize);
break;
}
// TODO::Handle remaining modes
switch (this.ToolTipContent.Placement)
{
case PlacementMode.Bottom:
offset.Offset(0, targetSize.Height);
break;
case PlacementMode.Top:
break;
}
// Enforce OnRender to update the visual with the latest instructions
this.ToolTipContent.InvalidateVisual();
return new[] { new CustomPopupPlacement(offset, PopupPrimaryAxis.None) };
}
private void AlignRightSide(Size popupSize, ref Point offset, Size targetSize)
{
offset = new Point(targetSize.Width, 0);
offset.Offset(-popupSize.Width, 0);
this.ToolTipContent.HorizontalToolTipArrowAlignment = HorizontalAlignment.Right;
}
private Side GetParentWindowClippingSide(Size targetSize)
{
Window parentWindow = Window.GetWindow(this.PlacementTarget);
var parentWindowBounds = new Rect(new Point(), parentWindow.RenderSize);
Point targetPositionInParentWindow = this.PlacementTarget.TranslatePoint(new Point(), parentWindow);
var targetBounds = new Rect(targetPositionInParentWindow, targetSize);
/* Check for clipping */
bool isTargetRightSideInParentWindowBounds = parentWindowBounds.Contains(targetBounds.TopRight);
if (!isTargetRightSideInParentWindowBounds)
{
return Side.Right;
}
// TODO::Check if Popup clips top or bottom Window bounds following the above pattern. If it does, switch to Bottom/Top placement
// to make it fit the parent's bounds by changing the OriginalPlacementMode accordingly.
// If you don't want to overwrite the original value introduce a dedicated property
// that you really depend on (for example OriginalPlacementModeInternal).
// Popup completely fits the parent using the desired placement.
return Side.None;
}
}
MyToolTipContent.cs
The custom control to make up the actual content of the Popup, responsible for drawing the visual geometries.
internal class MyToolTipContent : Control
{
public PlacementMode Placement
{
get => (PlacementMode)GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
public static readonly DependencyProperty PlacementProperty = DependencyProperty.Register(
"Placement",
typeof(PlacementMode),
typeof(MyToolTipContent),
new PropertyMetadata(default));
private const double ArrowHeight = 12;
private const double ArrowWidth = 12;
internal HorizontalAlignment HorizontalToolTipArrowAlignment { get; set; }
static MyToolTipContent()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyToolTipContent), new FrameworkPropertyMetadata(typeof(MyToolTipContent)));
}
protected override void OnRender(DrawingContext drawingContext)
{
SolidColorBrush fillBrush = Brushes.Transparent;
var strokePen = new Pen(Brushes.Black, 2);
// Make room for the stroke
this.Margin = new Thickness(strokePen.Thickness);
var toolTipGeometry = new PathGeometry();
/* Create geometry depending on tool tip position.
*
* TODO::Draw different geometries based on different placement.
*
*/
if (this.HorizontalToolTipArrowAlignment == HorizontalAlignment.Right
&& this.Placement == PlacementMode.Bottom)
{
// "Push" content down to make room for the arrow
this.Padding = new Thickness(0, ArrowHeight, 0, 0);
var arrowBoxLines = new PolyLineSegment(new[]
{
new Point(this.RenderSize.Width - ArrowWidth, ArrowHeight),
new Point(this.RenderSize.Width - ArrowWidth / 2, 0),
new Point(this.RenderSize.Width, ArrowHeight),
new Point(this.RenderSize.Width, this.RenderSize.Height),
new Point(0, this.RenderSize.Height),
}, true);
var arrowBoxPath = new PathFigure(
new Point(0, ArrowHeight),
new[] { arrowBoxLines },
true);
toolTipGeometry.Figures.Add(arrowBoxPath);
}
drawingContext.DrawGeometry(fillBrush, strokePen, toolTipGeometry);
base.OnRender(drawingContext);
}
}
Generic.xaml
<Style TargetType="{x:Type local:MyToolTipContent}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<Border Padding="{TemplateBinding Padding}">
<StackPanel>
<TextBlock Text="My popup title for bottom popup"
TextWrapping="Wrap" />
<TextBlock Text="My popup body content for bottom popup"
TextWrapping="Wrap" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Related

Awesomium WebControl WPF seems to misinterpret Scaling

I'm trying to build a simple prototype (that use Awesomium 1.7 RC3) that allows a WPF application to display a WebPage, allowing the page to be zoomed in / out. I don't want to preserve the layout, but adapt the layout in the same manner that you can resize the window. It's the same behavior as the zoom feature of Internet Explorer. The logical display area is raised while the actual rendering is zoomed.
For exemple, here is a screenshot of the app with a 100% zoom:
(source: hand-net.com)
The upper slider allows to control the zoom. When I change the zoom to 90% or 110%, here is what I get:
(source: hand-net.com)
And
(source: hand-net.com)
As you can see, the browser rendering is messy. Not only the internal rendering does not match the WebBrowser control area, but the picture resizing has a very low quality.
All of this were properly working with Awesomium 1.6.6.
How can I get the desired result?
The sample application can be downloaded here. The key parts are :
Xaml :
<Slider Value="{Binding Path=Zoom, Mode=TwoWay}"
IsSnapToTickEnabled="True"
TickPlacement="Both"
Grid.ColumnSpan="2"
Minimum="0.1"
Maximum="2.0"
TickFrequency="0.1"
LargeChange="0.1" />
<Grid x:Name="Container"
Background="SaddleBrown"
Grid.Row="1"
utils:SizeObserver.Observe="true"
utils:SizeObserver.ObservedWidth="{Binding ContainerActualWidth, Mode=OneWayToSource}"
utils:SizeObserver.ObservedHeight="{Binding ContainerActualHeight, Mode=OneWayToSource}">
<Grid x:Name="Containee"
Background="LightBlue"
RenderTransformOrigin="0,0"
Width="{Binding ContaineeWidth, Mode=OneWay}"
Height="{Binding ContaineeHeight, Mode=OneWay}">
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding Zoom, Mode=OneWay}"
ScaleY="{Binding Zoom, Mode=OneWay}" />
<SkewTransform />
<RotateTransform />
<TranslateTransform />
</TransformGroup>
</Grid.LayoutTransform>
<awe:WebControl Source="http://www.flickr.com/search/?q=strasbourg&z=m" />
</Grid>
</Grid>
ViewModel that is used as DataContext:
public class ViewPortViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
Debug.WriteLine("Property changes: " + propertyName);
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private double m_Zoom = 1D;
public double Zoom
{
get { return m_Zoom; }
set
{
if (value != m_Zoom)
{
m_Zoom = value;
RaisePropertyChanged("Zoom");
RaisePropertyChanged("ContaineeWidth");
RaisePropertyChanged("ContaineeHeight");
}
}
}
private double m_ContainerActualWidth = 100D;
public double ContainerActualWidth
{
get { return m_ContainerActualWidth; }
set
{
if (value != m_ContainerActualWidth)
{
m_ContainerActualWidth = value;
RaisePropertyChanged("ContainerActualWidth");
RaisePropertyChanged("ContaineeWidth");
}
}
}
private double m_ContainerActualHeight = 100D;
public double ContainerActualHeight
{
get { return m_ContainerActualHeight; }
set
{
if (value != m_ContainerActualHeight)
{
m_ContainerActualHeight = value;
RaisePropertyChanged("ContainerActualHeight");
RaisePropertyChanged("ContaineeHeight");
}
}
}
public double ContaineeWidth
{
get { return m_ContainerActualWidth / Zoom; }
}
public double ContaineeHeight
{
get { return m_ContainerActualHeight / Zoom; }
}
That looks similar to a problem I had with a previous version of the control where text was rendered in an unreadable way. The solution (for me) was to set RenderOptions.BitmapScalingMode to NearestNeighbor in my XAML. The answer I got from Awesomium indicated that this was a known issue that they were developing several strategies for, while I haven't noticed the problem since then it may well have reoccurred.
Check the question Issue with blurry text and see if it solves your issue. This was what my XAML ended up looking like:
<awe:WebControl x:Name="MyBrowser"
Grid.Row="1"
Focusable="True"
Visibility="Visible"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SnapsToDevicePixels="True"
>
<awe:WebControl.Style>
<Style>
<Setter Property="RenderOptions.BitmapScalingMode" Value="NearestNeighbor" />
</Style>
</awe:WebControl.Style>
</awe:WebControl>
I found the solution in my awesomium support forum question, gave by Perikles.
Two steps to solve my issue:
Also apply layout tranform to the webcontrol :
<Grid x:Name="Containee"
Background="LightBlue"
RenderTransformOrigin="0,0"
Width="{Binding ContaineeWidth, Mode=OneWay}"
Height="{Binding ContaineeHeight, Mode=OneWay}">
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding Zoom, Mode=OneWay}"
ScaleY="{Binding Zoom, Mode=OneWay}" />
</TransformGroup>
</Grid.LayoutTransform>
<awe:WebControl Source="http://www.flickr.com/search/?q=strasbourg&z=m"
LoadingFrameComplete="WebControl_LoadingFrameComplete_1">
<awe:WebControl.LayoutTransform>
<ScaleTransform ScaleX="{Binding Zoom, Mode=OneWay}"
ScaleY="{Binding Zoom, Mode=OneWay}" />
</awe:WebControl.LayoutTransform>
</awe:WebControl>
</Grid>
Subscribe to the LoadingFrameComplete event to apply rendering transform options after the page is loaded :
private bool renderingOptionsApplied;
private void WebControl_LoadingFrameComplete_1(object sender, Awesomium.Core.FrameEventArgs e)
{
if (renderingOptionsApplied)
return;
var webControl = sender as Awesomium.Windows.Controls.WebControl;
if ((webControl.Surface != null) && (webControl.Surface is WebViewPresenter))
{
RenderOptions.SetBitmapScalingMode(webControl.Surface as WebViewPresenter, BitmapScalingMode.Linear);
renderingOptionsApplied = true;
}
}

UserControl hosted in Panel

I've spent a lot of time with this problem. I have custom user control in wpf, It's toolbar with 10 buttons. This toolbar is added in Panel(winform panel) panel.Controls.Add(usercontrol). What I want is, respond on MouseLeave event from Panel.
I've tried panel.MouseLeave += MouseLeaveEvent, but event hasn't raised. No one event isn't raised(MouseMove, etc.). Is there any solution, how to make it, that MouseLeave event will be raised?
Thanks a lot.
EDIT
Here is the code.
CustomToolbar = new AxisMediaControlToolbarViewModel(this);
var toolbarView = new AxisMediaControlToolbarView { ViewModel = CustomToolbar };
var elementHost = new ElementHost { Dock = DockStyle.Fill, Child = toolbarView };
toolbarPanel.Controls.Add(elementHost);
In this thread are quite a few causes mentioned:
Set the Background of the UserControl
Make the Canvas focusable
Be aware of RenderTransforms
EDIT
I made a test and it works fine as long as the WPF element is not covering the Panel. The mouse has to be DIRECTLY over the panel to generate the event. The DockStyle.Fill might be the cause of your problem.
One of the causes may be that your panel's background is not set.
If you do not set the background, then the control is though to be as "its area is not to be taken care of" and effectively it is transparent, because it is not drawn. However, it has more consequences: it's area is not only not drawn, but also it is not tracked i.e. for mouse events.
For starters, try setting Background="Transparent". It may make little sense as it is already transparent, but this way the Panel control will have a background and if there are no other problems, it may immediatelly start seeing the mouse events.
edit: quickexample:
UserControl.xaml
<UserControl x:Class="UserControlMouse.MyButtonPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Background="LightBlue"
Tag="You will never see this message">
<StackPanel Margin="20" Background="LightGray"
MouseEnter="handler_MouseEnter" MouseMove="handler_MouseMove" MouseLeave="handler_MouseLeave"
Tag="StackPanel">
<StackPanel.Resources>
<Style TargetType="Border">
<Setter Property="Margin" Value="2" />
<Setter Property="Padding" Value="5" />
<Setter Property="Background" Value="Gray" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="TextAlignment" Value="Center" />
<Setter Property="Background" Value="WhiteSmoke" />
</Style>
</StackPanel.Resources>
<Button Content="#1" MaxWidth="80" Tag="Button#1" />
<Button Content="#2" MaxWidth="80" Tag="Button#2"
MouseEnter="handler_MouseEnter" MouseMove="handler_MouseMove" MouseLeave="handler_MouseLeave"
/>
<Button Content="#3" MaxWidth="80" Tag="Button#3" />
<Button Content="#4" MaxWidth="80" Tag="Button#4" />
<Button Content="#5" MaxWidth="80" Tag="Button#5"
MouseEnter="handler_MouseEnter" MouseMove="handler_MouseMove" MouseLeave="handler_MouseLeave"
/>
<Button Content="#6" MaxWidth="80" Tag="Button#6" />
<Button Content="#7" MaxWidth="80" Tag="Button#7" />
<Button Content="#8" MaxWidth="80" Tag="Button#8" />
<TextBlock Margin="0,20,0,0" Tag="Comment TextBlock #1">
<Run Text="Sender was: " />
<Run Text="{Binding ReportingControl}" />
</TextBlock>
<TextBlock Tag="Comment TextBlock #2">
<Run Text="Source was: " />
<Run Text="{Binding ContainerControl}" />
</TextBlock>
<TextBlock Tag="Comment TextBlock #3">
<Run Text="Now you are in: " />
<Run Text="{Binding ControlUnderMouse}" />
</TextBlock>
<UniformGrid Columns="3" Tag="indicator UniformGrid">
<Border Visibility="{Binding Visib_OnEnter}" Tag="indicator border #1">
<TextBlock Text="Enter" />
</Border>
<Border Visibility="{Binding Visib_OnMove}" Tag="indicator border #2"
MouseEnter="handler_MouseEnter" MouseMove="handler_MouseMove" MouseLeave="handler_MouseLeave">
<TextBlock Text="Move" />
</Border>
<Border Visibility="{Binding Visib_OnLeave}" Tag="indicator border #3">
<TextBlock Text="Leave" />
</Border>
</UniformGrid>
</StackPanel>
</UserControl>
usercontrol.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace UserControlMouse
{
public partial class MyButtonPanel : UserControl
{
private SimpleDataObject theData = new SimpleDataObject();
public MyButtonPanel()
{
InitializeComponent();
this.DataContext = theData;
}
private const int COUNTER_LAG = 20;
private int counter_enter = 0;
private void handler_MouseEnter(object sender, MouseEventArgs e)
{
counter_enter = counter_mouse;
theData.Visib_OnEnter = System.Windows.Visibility.Visible;
theData.Visib_OnMove = System.Windows.Visibility.Hidden;
theData.Visib_OnLeave = System.Windows.Visibility.Hidden;
theData.ReportingControl = makeDescriptionOfControl(sender);
theData.ContainerControl = makeDescriptionOfControl(e.Source);
theData.ControlUnderMouse = makeDescriptionOfControl(null);
}
private int counter_mouse = 0;
private void handler_MouseMove(object sender, MouseEventArgs e)
{
++counter_mouse;
if (counter_mouse > counter_enter + COUNTER_LAG) theData.Visib_OnEnter = System.Windows.Visibility.Hidden;
theData.Visib_OnMove = System.Windows.Visibility.Visible;
if (counter_mouse > counter_leave + COUNTER_LAG) theData.Visib_OnLeave = System.Windows.Visibility.Hidden;
theData.ReportingControl = makeDescriptionOfControl(sender);
theData.ContainerControl = makeDescriptionOfControl(e.Source);
theData.ControlUnderMouse = makeDescriptionOfControl(null);
}
private int counter_leave = 0;
private void handler_MouseLeave(object sender, MouseEventArgs e)
{
counter_leave = counter_mouse;
theData.Visib_OnEnter = System.Windows.Visibility.Hidden;
theData.Visib_OnMove = System.Windows.Visibility.Hidden;
theData.Visib_OnLeave = System.Windows.Visibility.Visible;
theData.ReportingControl = makeDescriptionOfControl(sender);
theData.ContainerControl = makeDescriptionOfControl(e.Source);
theData.ControlUnderMouse = makeDescriptionOfControl(null);
}
private string makeDescriptionOfControl(object uiobj)
{
if (uiobj == null)
return "???";
string text = uiobj.GetType().Name;
var fe = uiobj as FrameworkElement;
if (fe != null && fe.Tag != null)
text += " (" + (string)((FrameworkElement)uiobj).Tag + ")";
return text;
}
}
public class SimpleDataObject : DependencyObject
{
public string ControlUnderMouse { get { return (string)GetValue(ControlUnderMouseProperty); } set { SetValue(ControlUnderMouseProperty, value); } }
public static readonly DependencyProperty ControlUnderMouseProperty = DependencyProperty.Register("ControlUnderMouse", typeof(string), typeof(SimpleDataObject), new UIPropertyMetadata("???"));
public string ReportingControl { get { return (string)GetValue(ReportingControlProperty); } set { SetValue(ReportingControlProperty, value); } }
public static readonly DependencyProperty ReportingControlProperty = DependencyProperty.Register("ReportingControl", typeof(string), typeof(SimpleDataObject), new UIPropertyMetadata("???"));
public string ContainerControl { get { return (string)GetValue(ContainerControlProperty); } set { SetValue(ContainerControlProperty, value); } }
public static readonly DependencyProperty ContainerControlProperty = DependencyProperty.Register("ContainerControl", typeof(string), typeof(SimpleDataObject), new UIPropertyMetadata("???"));
public Visibility Visib_OnEnter { get { return (Visibility)GetValue(Visib_OnEnterProperty); } set { SetValue(Visib_OnEnterProperty, value); } }
public static readonly DependencyProperty Visib_OnEnterProperty = DependencyProperty.Register("Visib_OnEnter", typeof(Visibility), typeof(SimpleDataObject), new UIPropertyMetadata(Visibility.Hidden));
public Visibility Visib_OnMove { get { return (Visibility)GetValue(Visib_OnMoveProperty); } set { SetValue(Visib_OnMoveProperty, value); } }
public static readonly DependencyProperty Visib_OnMoveProperty = DependencyProperty.Register("Visib_OnMove", typeof(Visibility), typeof(SimpleDataObject), new UIPropertyMetadata(Visibility.Hidden));
public Visibility Visib_OnLeave { get { return (Visibility)GetValue(Visib_OnLeaveProperty); } set { SetValue(Visib_OnLeaveProperty, value); } }
public static readonly DependencyProperty Visib_OnLeaveProperty = DependencyProperty.Register("Visib_OnLeave", typeof(Visibility), typeof(SimpleDataObject), new UIPropertyMetadata(Visibility.Hidden));
}
}
sample window
<Window x:Class="UserControlMouse.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:UserControlMouse"
Title="MainWindow" Height="350" Width="525">
<Grid Background="Red">
<my:MyButtonPanel Margin="10" />
</Grid>
</Window>
Please run it and play with it.
Observe how the events are fired and which controls see them. Note how SENDER and SOURCE are changing
note that SENDER is always the same, the root of the mouse-capture. It is the Source that changes
note that only button #2 and button #5 is reporting ENTER/LEAVE (they have direct handlers, others have not!)
note that other controls are 'detectable' with the mouse-move even if they don't have mouse handlers - you can get events with Source(s) that does not have any mouse capture set
check the OriginalSource (the last text line) - this is the control directly under mouse - and often it is not the control you would want to notice, as it is some "leaf control" from some templates or styles..

Dynamically change the width of a WPF window when content gets visible

I want to be able to hide the left hand side of the screen and right hand side of the screen at the beginning of the program.
Then when the user presses create new button the left hand side of the screen becomes available so they can create the new item. Then when they press save it comes back to the middle datagrid only.
Then I want to add an event when the double click on the datagrid row (data is programmed in to the datagrid in the code) the right hand side of the screen becomes visible then when the button allocate is pressed the right hand side disappears again just leaving the datagrid.
I am fairly new to WPF so unsure whether this can be done or not. I am trying to do it in the same window at the moment I am making prototypes for my company and already have some that use separate windows. I had posted an image but am un able to post it as I am a new user.
To hide and show controls I would either recommend using expanders (as your comment says you've done) or Grids and setting their visibility as needed. If you want your side panels to appear over the datagrid then displaying them is just a matter of changing their visibility. If don't want obscure the DataGrid then you will need to change the visibility of the panels as well as the size of the window.
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow">
<Grid>
<!-- DataGrid display -->
<Grid>
<StackPanel>
<Button Content="Add New" Click="OnAddNewButtonClick" Width="100"/>
<DataGrid ItemsSource="{Binding GridItems}" IsReadOnly="True" Name="dataGrid">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseDoubleClick" Handler="OnRowDoubleClick"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</StackPanel>
</Grid>
<!-- Left column pops up over DataGrid -->
<Grid Name="LeftColumn" Visibility="Collapsed" Background="Red" Width="200" HorizontalAlignment="Left">
<StackPanel VerticalAlignment="Center">
<Button Content="Hide Column" Click="OnLeftColumnButtonClick"/>
</StackPanel>
</Grid>
<!-- Right Column expands screen size-->
<Grid Visibility="Collapsed" Name="RightColumn" Width="200" HorizontalAlignment="Right">
<StackPanel Background="Green" >
<TextBlock Text="Hidden Column"/>
<Button Content="Hide Panel" Click="OnRightColumnButtonClick"/>
</StackPanel>
</Grid>
</Grid>
</Window
C# - I know you're working in VB, but this was quicker for me. The code should be fairly self explanatory, but if you need a VB sample let me know:
public partial class MainWindow : Window
{
public ObservableCollection<Person> GridItems { get; set; }
private const double CollapsedWidth = 500;
private const double ExpandedWidth = 700;
public MainWindow()
{
DataContext = this;
GridItems = new ObservableCollection<Person>();
GridItems.Add(new Person { Name = "Foo", Age = 1 });
GridItems.Add(new Person { Name = "Bar", Age = 2 });
InitializeComponent();
Width = CollapsedWidth;
}
private void OnAddNewButtonClick(object sender, RoutedEventArgs e)
{
LeftColumn.Visibility = Visibility.Visible;
}
private void OnLeftColumnButtonClick(object sender, RoutedEventArgs e)
{
LeftColumn.Visibility = Visibility.Collapsed;
}
private void OnRowDoubleClick(object sender, MouseButtonEventArgs e)
{
Width = ExpandedWidth;
RightColumn.Visibility = Visibility.Visible;
}
private void OnRightColumnButtonClick(object sender, RoutedEventArgs e)
{
RightColumn.Visibility = Visibility.Collapsed;
Width = CollapsedWidth;
}
}

How to enable horizontal scrolling with mouse?

I cannot determine how to scroll horizontally using the mouse wheel. Vertical scrolling works well automatically, but I need to scroll my content horizontally. My code looks like this:
<ListBox x:Name="receiptList"
Margin="5,0"
Grid.Row="1"
ItemTemplate="{StaticResource receiptListItemDataTemplate}"
ItemsSource="{Binding OpenReceipts}"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
ScrollViewer.HorizontalScrollBarVisibility="Visible"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
My item template looks like this:
<DataTemplate x:Key="receiptListItemDataTemplate">
<RadioButton GroupName="Numbers"
Command="{Binding Path=DataContext.SelectReceiptCommand,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type POS:PointOfSaleControl}}}"
CommandParameter="{Binding }"
Margin="2,0"
IsChecked="{Binding IsSelected}">
<RadioButton.Template>
<ControlTemplate TargetType="{x:Type RadioButton}" >
<Grid x:Name="receiptGrid" >
<Grid>
<Border BorderThickness="2"
BorderBrush="Green"
Height="20"
Width="20">
<Grid x:Name="radioButtonGrid"
Background="DarkOrange">
<TextBlock x:Name="receiptLabel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=NumberInQueue, Mode=OneWay}"
FontWeight="Bold"
FontSize="12"
Foreground="White">
</TextBlock>
</Grid>
</Border>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Margin"
TargetName="receiptGrid"
Value="2,2,-1,-1"/>
<Setter Property="Background"
TargetName="radioButtonGrid"
Value="Maroon"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</DataTemplate>
Is there another method or control that I need to add to get that functionality?
Here's a complete behaviour. Add the below class to your code, then in your XAML set the attached property to true on any UIElement that contains a ScrollViewer as a visual child.
<MyVisual ScrollViewerHelper.ShiftWheelScrollsHorizontally="True" />
The class:
public static class ScrollViewerHelper
{
public static readonly DependencyProperty ShiftWheelScrollsHorizontallyProperty
= DependencyProperty.RegisterAttached("ShiftWheelScrollsHorizontally",
typeof(bool),
typeof(ScrollViewerHelper),
new PropertyMetadata(false, UseHorizontalScrollingChangedCallback));
private static void UseHorizontalScrollingChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as UIElement;
if (element == null)
throw new Exception("Attached property must be used with UIElement.");
if ((bool)e.NewValue)
element.PreviewMouseWheel += OnPreviewMouseWheel;
else
element.PreviewMouseWheel -= OnPreviewMouseWheel;
}
private static void OnPreviewMouseWheel(object sender, MouseWheelEventArgs args)
{
var scrollViewer = ((UIElement)sender).FindDescendant<ScrollViewer>();
if (scrollViewer == null)
return;
if (Keyboard.Modifiers != ModifierKeys.Shift)
return;
if (args.Delta < 0)
scrollViewer.LineRight();
else
scrollViewer.LineLeft();
args.Handled = true;
}
public static void SetShiftWheelScrollsHorizontally(UIElement element, bool value) => element.SetValue(ShiftWheelScrollsHorizontallyProperty, value);
public static bool GetShiftWheelScrollsHorizontally(UIElement element) => (bool)element.GetValue(ShiftWheelScrollsHorizontallyProperty);
[CanBeNull]
private static T FindDescendant<T>([CanBeNull] this DependencyObject d) where T : DependencyObject
{
if (d == null)
return null;
var childCount = VisualTreeHelper.GetChildrenCount(d);
for (var i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(d, i);
var result = child as T ?? FindDescendant<T>(child);
if (result != null)
return result;
}
return null;
}
}
This answer fixes a few bugs in Johannes' answer, such as not filtering by Shift key, scrolling both horizontally and vertically at the same time (motion was diagonal) and the inability to disable the behaviour by setting the property to false.
I wrote an Attached Property for this purpose to reuse it on every ItemsControl containing an ScrollViewer. FindChildByType is an Telerik extension but can also be found here.
public static readonly DependencyProperty UseHorizontalScrollingProperty = DependencyProperty.RegisterAttached(
"UseHorizontalScrolling", typeof(bool), typeof(ScrollViewerHelper), new PropertyMetadata(default(bool), UseHorizontalScrollingChangedCallback));
private static void UseHorizontalScrollingChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
ItemsControl itemsControl = dependencyObject as ItemsControl;
if (itemsControl == null) throw new ArgumentException("Element is not an ItemsControl");
itemsControl.PreviewMouseWheel += delegate(object sender, MouseWheelEventArgs args)
{
ScrollViewer scrollViewer = itemsControl.FindChildByType<ScrollViewer>();
if (scrollViewer == null) return;
if (args.Delta < 0)
{
scrollViewer.LineRight();
}
else
{
scrollViewer.LineLeft();
}
};
}
public static void SetUseHorizontalScrolling(ItemsControl element, bool value)
{
element.SetValue(UseHorizontalScrollingProperty, value);
}
public static bool GetUseHorizontalScrolling(ItemsControl element)
{
return (bool)element.GetValue(UseHorizontalScrollingProperty);
}
The easiest way is to add PreviewMouseWheel listener to the ScrollViewer, check for shift (or whatever you want to do to indicate horizontal scroll), and then call LineLeft or LineRight (or PageLeft / PageRight) depending on the value of the Delta value of the MouseWheelEventArgs
I was kinda looking for the most simple way to make any ScrollViewer scroll left-right instead of up-down. So here is the simplest combination of the other answers.
<ScrollViewer HorizontalScrollBarVisibility="Visible"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
and:
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
ScrollViewer scrollViewer = (ScrollViewer)sender;
if (e.Delta < 0)
{
scrollViewer.LineRight();
}
else
{
scrollViewer.LineLeft();
}
e.Handled = true;
}
This is basically what John Gardner suggested just with the actual code. I'm also quoting my answer from similar question here.
(sender as ScrollViewer).ScrollToHorizontalOffset( (sender as ScrollViewer).ContentHorizontalOffset + e.Delta);
Try this:
<ListBox x:Name="receiptList"
Margin="5,0"
Grid.Row="1"
ItemTemplate="{StaticResource receiptListItemDataTemplate}"
ItemsSource="{Binding OpenReceipts}"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
UPDATE Ooops, missed the part about the mouse wheel! Sorry
To make the mouse wheel work, you will have to subscribe to the mouse wheel event and manuaaly move the scroll bar... This can nicelly be encapsulated by a behavior but I think thats the only way to make it work!

WPF Template: AdornedElement not showing!

I want to add some elements to a TextBox by using a Template with the extra elements and the original TextBox inserted in the right spot. I'm trying to use the AdornedElementPlaceholder just like you would do when making a Validation.ErrorTemplate But the AdornedElement is not showing up. I have simplified the example as much as possible:
<TextBox Text="Border me with my Template">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border BorderBrush="Green" BorderThickness="1">
<AdornedElementPlaceholder/>
</Border>
</ControlTemplate>
</TextBox.Template>
</TextBox>
The result is just a green box around the space that should be my textbox!
Unfortunately, that's just not how it works. However, it's not much more difficult than that. If you want to add a green border around a text box by changing the control template, it's going to look a bit more like this:
<ControlTemplate TargetType="TextBox">
<Border x:Name="Border"
CornerRadius="2"
Background="{TemplateBinding Background}"
BorderBrush="Green"
BorderThickness="1"
SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
</ControlTemplate>
The important part in there is the ScrollViewer named PART_ContentHost. The TextBox looks for that guy when the template is applied and uses it to host the text, caret, etc. What I think you're missing is that when you override the Template for an element, you're overriding the entire control template. It's unfortunate that you can't just change bits and pieces, but that's the truth of the matter.
Of course, if you want to maintain the original look and feel of the TextBox, such that it still looks like a Win7 TextBox for instance, you'd need to do a bit more in the ControlTemplate.
For what it's worth, it looks like the template you were trying to apply would work if you're talking about using an Adorner. It's similar to how the validation templates work in WPF, but that's a whole-nother story.
Oh, and for a much simpler way to change the border to green, you should be able to just set the BorderBrush property on the TextBox itself. Of course, I really don't know exactly what you're going for.
--
HTH
Dusty
I ended up doing a Custom Control based on a HeaderedContent Control.
It will allow the user to click or hoover over some content and then show a bubble containing some other or the same content.
Usage:
<JsCustomControls:BaloonBox LabelText="{Binding Note}" WaterMark="Click to write Note" Type="ClickToShow">
<JsCustomControls:BaloonBox.Content>
<TextBox AcceptsReturn="True" Text="{Binding Path=Note}" TextWrapping="Wrap"></TextBox>
</JsCustomControls:BaloonBox.Content>
</JsCustomControls:BaloonBox>
The code for the User control:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace JsCustomControls
{
public enum BaloonBoxTypeEnum
{
ClickToShow,
MouseOverToShow,
Manual
}
[TemplatePart(Name = BaloonBox.HeaderElement, Type = typeof(ContentPresenter))]
[TemplatePart(Name = BaloonBox.ContentElement, Type = typeof(ContentPresenter))]
[TemplatePart(Name = BaloonBox.PopUpElement, Type = typeof(Popup))]
[TemplatePart(Name=BaloonBox.LabelElement,Type=typeof(Label))]
public class BaloonBox : HeaderedContentControl
{
DispatcherTimer PopupTimer = new DispatcherTimer();
private const string HeaderElement = "PART_HeaderContentControl";
private const string ContentElement = "PART_ContenContentControl";
private const string PopUpElement = "PART_PopUp";
private const string LabelElement = "PART_HeaderLabel";
private ContentPresenter headerContentControl;
private ContentPresenter contentContentControl;
private Popup popUp;
private Label headerLabel;
static BaloonBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BaloonBox), new FrameworkPropertyMetadata(typeof(BaloonBox)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
headerContentControl = GetTemplateChild(HeaderElement) as ContentPresenter;
contentContentControl = GetTemplateChild(ContentElement) as ContentPresenter;
popUp = GetTemplateChild(PopUpElement) as Popup;
headerLabel = GetTemplateChild(LabelElement) as Label;
if (headerContentControl != null) headerContentControl.MouseDown += new MouseButtonEventHandler(headerContentControl_MouseDown);
if(headerLabel!=null)headerLabel.MouseDown+=new MouseButtonEventHandler(headerContentControl_MouseDown);
if (headerContentControl != null) headerContentControl.MouseMove += new MouseEventHandler(headerContentControl_MouseMove);
if(headerLabel!=null)headerLabel.MouseMove+=new MouseEventHandler(headerContentControl_MouseMove);
PopupTimer = new DispatcherTimer();
PopupTimer.Tick += new EventHandler(PopupTimer_Tick);
if(string.IsNullOrEmpty(LabelText))
{
if (headerLabel != null) headerLabel.Foreground = Brushes.Gray;
if (headerLabel != null) headerLabel.Content = WaterMark;
}
else
{
if (headerLabel != null) headerLabel.Foreground = Brushes.Black;
if (headerLabel != null) headerLabel.Content = LabelText;
}
}
void headerContentControl_MouseMove(object sender, MouseEventArgs e)
{
if (Type == BaloonBoxTypeEnum.MouseOverToShow)
{
if (popUp != null) popUp.IsOpen = true;
PopupTimer.Interval = new TimeSpan(0, 0, 0, 2);
PopupTimer.Start();
}
}
void headerContentControl_MouseDown(object sender, MouseButtonEventArgs e)
{
if (Type == BaloonBoxTypeEnum.ClickToShow)
{
if (popUp != null) popUp.IsOpen = true;
PopupTimer.Interval = new TimeSpan(0, 0, 0, 3);
PopupTimer.Start();
}
}
void PopupTimer_Tick(object sender, EventArgs e)
{
if (!headerContentControl.IsMouseOver && !contentContentControl.IsMouseOver && !contentContentControl.IsKeyboardFocusWithin)
{
PopupTimer.Stop();
popUp.IsOpen = false;
}
}
public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register("IsOpen",
typeof (bool),
typeof (BaloonBox),
new FrameworkPropertyMetadata
(new PropertyChangedCallback
(OnIsOpenChanged)));
public bool IsOpen
{
get { return (bool) GetValue(IsOpenProperty); }
set{SetValue(IsOpenProperty,value);}
}
private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BaloonBox baloonBox = (BaloonBox)d;
baloonBox.popUp.IsOpen =(bool)e.NewValue;
}
public static readonly DependencyProperty WaterMarkProperty = DependencyProperty.Register("WaterMark",
typeof (string),
typeof (BaloonBox),
new FrameworkPropertyMetadata
(new PropertyChangedCallback
(OnWatermarkChanged)));
public string WaterMark
{
get { return (string)GetValue(WaterMarkProperty); }
set { SetValue(WaterMarkProperty,value); }
}
private static void OnWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register("LabelText",
typeof (string),
typeof (BaloonBox)
,new FrameworkPropertyMetadata(new PropertyChangedCallback(OnLabelTextChanged)));
public string LabelText
{
get { return (string) GetValue(LabelTextProperty); }
set
{
SetValue(LabelTextProperty,value);
headerLabel.Content = value;
}
}
private static void OnLabelTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BaloonBox baloonBox = (BaloonBox)d;
if (string.IsNullOrEmpty(e.NewValue.ToString()))
{
if (baloonBox.headerLabel != null) baloonBox.headerLabel.Foreground = Brushes.Gray;
if (baloonBox.headerLabel != null) baloonBox.headerLabel.Content = baloonBox.WaterMark;
}
else
{
if (baloonBox.headerLabel != null) baloonBox.headerLabel.Foreground = Brushes.Black;
if (baloonBox.headerLabel != null) baloonBox.headerLabel.Content = e.NewValue;
}
}
public static readonly DependencyProperty BaloonBoxTypeProperty = DependencyProperty.Register(
"BaloonBoxType", typeof(BaloonBoxTypeEnum), typeof(BaloonBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnBaloonBoxTypeChanged)));
public BaloonBoxTypeEnum Type
{
get { return (BaloonBoxTypeEnum) GetValue(BaloonBoxTypeProperty);}
set { SetValue(BaloonBoxTypeProperty,value);}
}
private static void OnBaloonBoxTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//who cares?
}
}
}
</Border>
<Popup x:Name="PART_PopUp" HorizontalOffset="-50" VerticalOffset="-5" AllowsTransparency="True" IsOpen="False" PopupAnimation="Slide" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Header}">
<Grid Height="150" Width="250" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2"></ColumnDefinition>
<ColumnDefinition Width="0.050*"></ColumnDefinition>
<ColumnDefinition Width="0.900*"></ColumnDefinition>
<ColumnDefinition Width="0.050*"></ColumnDefinition>
<ColumnDefinition Width="2"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2"></RowDefinition>
<RowDefinition Height="0.300*"></RowDefinition>
<RowDefinition Height="0.700*"></RowDefinition>
<RowDefinition Height="0.100*"></RowDefinition>
<RowDefinition Height="2"></RowDefinition>
</Grid.RowDefinitions>
<Path Grid.Row="1" Grid.RowSpan="3" Grid.Column="1" Grid.ColumnSpan="3" Data="M79,279 L119,279 135,263 135,279 319,279 319,368.5 78.5,368.5 z" Fill="#FFFDFDB3" Stretch="Fill" Stroke="Black" UseLayoutRounding="False">
</Path>
<ScrollViewer Grid.Row="2" Grid.Column="2" Grid.RowSpan="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentPresenter x:Name="PART_ContenContentControl" Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"/>
</ScrollViewer>
</Grid>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Resources