WPF ToolBarPanel width not changed - wpf

I have a Canvas and a ToolBarPanel inside it.
I want to resize the ToolBarPanel using a Thumb, so I placed the Thumb inside the ToolBarPanel. When I catch the DragDelta of the Thumb, the width is not changed. Below is the code:
<Canvas x:Uid="userControlCanvas" Name="userControlCanvas" Width="300" Height="300">
<ToolBarPanel x:Uid="categoriesToolBar" Name="categoriesToolBar" Visibility="Visible"
Height="300" Width="10" Panel.ZIndex="100"
Background="#CCDCDCDC"
SizeChanged="toolbarSizeChanged">
<Thumb x:Name="splitPane" Cursor="SizeWE" Height="300" Canvas.Left="2" Width="8" DragStarted="splitPaneDragStarted" DragDelta="splitPaneDragging">
</Thumb>
</ToolBarPanel>
</Canvas>
And the cs code:
private void splitPaneDragging(object sender, DragDeltaEventArgs e) {
categoriesToolBar.Width = categoriesToolBar.ActualWidth + e.HorizontalChange;
//categoriesToolBar.InvalidateMeasure();
categoriesToolBar.UpdateLayout();
//categoriesToolBar.InvalidateArrange();
//categoriesToolBar.InvalidateVisual();
//categoriesToolBar.InvalidateMeasure();
//Width is always the initial width (150)
Console.WriteLine("ch=" + e.HorizontalChange + " tw=" + categoriesToolBar.Width + " aw=" + categoriesToolBar.ActualWidth);
}
private void toolbarSizeChanged(object sender, SizeChangedEventArgs e) {
// this is not fired when I try to change the width in splitPaneDragging method
}

Related

WPF Popup positioning and drawn issues

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>

WPF - Moving a Canvas

I have trouble with moving a canvas in a Window.
<Canvas x:Name="Canvas1" Height="200" Grid.Column="1" Margin="10,15,92,54" Grid.Row="1" Background="#FFECECEC" Grid.RowSpan="2" MouseDown="Canvas_MouseDown" MouseUp="Canvas_MouseUp" MouseMove="Canvas_MouseMove" >
<TextBox Height="100" Width="100" Margin="50,50,327,65" Background="Red"/>
<TextBox Height="100" Width="100" Margin="10,15,327,65" Background="Blue" />
<Canvas.RenderTransform>
<TransformGroup>
<TranslateTransform x:Name="translate" />
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
This is code behind:
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
Canvas1.CaptureMouse();
point = Mouse.GetPosition(Grid1);
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (Canvas1.IsMouseCaptured)
{
translate.X = e.GetPosition(Grid1).X - point.X;
translate.Y = e.GetPosition(Grid1).Y - point.Y;
}
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
Canvas1.ReleaseMouseCapture();
}
First time, it seem woork fine.
But second time i try to click on my canvas, this move but from initial position.
How can i fix it?
When initializing point on mouse down, subtract the current translation:
private void Canvas1_MouseDown(object sender, MouseButtonEventArgs e)
{
Canvas1.CaptureMouse();
point = Mouse.GetPosition(Grid1);
point.X -= translate.X;
point.Y -= translate.Y;
}
Alternatively, always only add the difference vector:
private void Canvas1_MouseDown(object sender, MouseButtonEventArgs e)
{
Canvas1.CaptureMouse();
point = Mouse.GetPosition(Grid1);
}
private void Canvas1_MouseMove(object sender, MouseEventArgs e)
{
if (Canvas1.IsMouseCaptured)
{
var p = e.GetPosition(Grid1);
var diff = p - point;
point = p;
translate.X += diff.X;
translate.Y += diff.Y;
}
}
Also make sure the Grid doesn't position the Canvas, by setting the Canvas alignment to Top/Left:
<Canvas x:Name="Canvas1" HorizontalAlignment="Left" VerticalAlignment="Top" ...>
And probably don't set a Margin, but instead initialize the TranslateTransform appropriately.

Mouse click on WPF (Time)Slider has no effect when slider thumb is moving bound to a timer

I use MediaElement and (Time)Slider to play and control playing of a video.
I used answer 2 to this question as a base.
In addition to the dragging capability I would also like the slider thumb to be moved to a mouse click point.
This works ok when the MediaElement and (Time)Slider are paused, but when the video is playing a mouse click has no effect
Here is my code
XAML:
<MediaElement Source="..."
Name="mediaView"
Height="450" LoadedBehavior="Manual" UnloadedBehavior="Stop" Stretch="UniformToFill"
MediaOpened="OnMediaOpened" MediaEnded="OnMediaEnded" MediaFailed="OnMediaFailed" Grid.Row="0" Grid.Column="0"/>
<Grid Name="mediaBar" VerticalAlignment="Bottom" Margin="5,10,5,0" Background="#B2282828" Grid.Row="0" Grid.Column="0">
<!-- ... -->
<Slider Name="timeSlider" Margin="5,5,5,0"
Thumb.DragStarted="OnDragStarted" Thumb.DragCompleted="OnDragCompleted" ValueChanged="OnTimeSliderValueChanged"
PreviewMouseLeftButtonUp="OnMouseLeftButtonUp" IsMoveToPointEnabled="True"
MinWidth="200" FlowDirection="LeftToRight"
Grid.Column="4" Cursor="ScrollWE" VerticalAlignment="Center"/>
<!-- ... -->
</Grid>
relevant c# part:
private void OnDragStarted(object sender, DragStartedEventArgs args)
{
isDragging = true;
ticks.Stop();
}
private void OnDragCompleted(object sender, DragCompletedEventArgs args)
{
isDragging = false;
int SliderValue = (int)timeSlider.Value;
TimeSpan ts = new TimeSpan(0, 0, 0, 0, SliderValue);
mediaView.Position = ts;
if(currentStatus == Status.PLAYING)
ticks.Start();
}
private void OnMouseLeftButtonUp(object sender, EventArgs ea)
{
if(!isDragging)
{
mediaView.Pause();
ticks.Stop();
int SliderValue = (int)timeSlider.Value; // when video is playing this not the point of the mouse click
// ...
}
}
I can understand that timeSlider.Value delivers the current point in time instead of the mouse click position when the video is playing.
Is there another way to measure the position of the mouse click and update the slider value with that ?
Or a better solution for the Mouse-click-while-slider-is-running-situation ?
Try this:
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs ea)
{
mediaView.Position = TimeSpan.FromSeconds((int)(mediaView.NaturalDuration.TimeSpan.TotalSeconds * (double)(ea.GetPosition(timeSlider).X / this.Width)));
}

WPF Popup wrapping when dragging vertically

I have a project that uses a System.Windows.Controls.Primatives.Popup to drag a 'tooltip' like control along with a mouse.
Whenever the drag crosses a horizontal line the popup 'wraps' to the bottom of the screen - despite having sane values for the VerticalOffset. The point at which this wrapping occurs appears to be tied to the HEIGHT of the window, but not it's position.
Here's the code from the sandbox project I have created that also exhibits the same behavior:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.MainGrid.MouseDown += Grid_MouseDown;
this.MainGrid.MouseUp += Grid_MouseUp;
this.MainGrid.MouseMove += (s, e) => { if (this.Popup.IsOpen) { Popup_Drag(s, e); } };
this.Popup.MouseMove += Popup_Drag;
}
private void Popup_Drag(object sender, MouseEventArgs e)
{
Popup.HorizontalOffset = e.GetPosition(this.Popup).X;
Popup.VerticalOffset = e.GetPosition(this.Popup).Y;
this.Status_Top.Text = String.Format("Height/Top: {0}/{1} Width/Left: {2}/{3}", this.Height, this.Top, this.Width, this.Left);
this.Status.Text = String.Format("Vertical Offset: {0} Horizontal Offset: {1}", Popup.VerticalOffset, Popup.HorizontalOffset);
}
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
this.Popup.IsOpen = false;
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
this.Popup.IsOpen = true;
Popup_Drag(sender, e);
}
}
And the Window XAML:
<Window x:Class="WpfSandbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="MainGrid" Background="Purple">
<TextBlock x:Name="Status_Top"></TextBlock>
<Popup x:Name="Popup" Cursor="Hand" HorizontalAlignment="Left"
VerticalAlignment="Bottom" IsOpen="True">
<TextBlock Background="Blue" Foreground="White">
<TextBlock x:Name="Status">TEXT</TextBlock></TextBlock>
</Popup>
</Grid>
</Window>
I was able to fix this by adding Placement="RelativePoint" to the Popup attributes. Apparently this is the default in Silverlight, but not WPF.

WPF Image Panning Constraints

I'm trying to figure out how to constrain a pan so that the image remains entirely within the bounds of it's containing border. Any help this regard would be greatly appreciated. Thanks....
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MapTest.Window1"
Title="PanTest"
Width="484"
Height="400"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Margin="8,8,8,0"
TextWrapping="Wrap">
To pan the image, simply click on it then drag. I'd like to
constrain the pan so that the image remains entirely within
the border, but without resorting to ClipToBounds. Any help
in this regard would be greatly appreciated. Thanks....</TextBlock>
<Border x:Name="border"
Margin="8"
BorderBrush="Black"
BorderThickness="1"
Grid.Row="1">
<Image x:Name="image"
Source="Penguins.jpg"
Opacity="1"
Width="300"
Height="300"
Stretch="Uniform"
RenderTransformOrigin="0.5,0.5" />
</Border>
</Grid>
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace MapTest
{
public partial class Window1 : Window
{
private Point origin;
private Point start;
public Window1()
{
InitializeComponent();
var group = new TransformGroup();
group.Children.Add(new TranslateTransform());
image.RenderTransform = group;
image.MouseLeftButtonDown += image_MouseLeftButtonDown;
image.MouseMove += image_MouseMove;
image.MouseLeftButtonUp += image_MouseLeftButtonUp;
}
private void image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
image.ReleaseMouseCapture();
}
private void image_MouseMove(object sender, MouseEventArgs e)
{
if (!image.IsMouseCaptured)
return;
var tt = (TranslateTransform)((TransformGroup)image.RenderTransform).
Children.First(tr => tr is TranslateTransform);
var vector = start - e.GetPosition(border);
tt.X = origin.X - vector.X;
tt.Y = origin.Y - vector.Y;
}
private void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
image.CaptureMouse();
var tt = (TranslateTransform)((TransformGroup)image.RenderTransform).
Children.First(tr => tr is TranslateTransform);
start = e.GetPosition(border);
origin = new Point(tt.X, tt.Y);
}
}
}
The following code gives you the bounds of the transformed Image control in coordinates relative to the Border control. You could easily check if the bounds are located inside the Border control.
var rect = new Rect(image.RenderSize);
var bounds = image.TransformToAncestor(border).TransformBounds(rect);

Resources