I was wondering is it possible to limit child window's ability to be moved around to only within parent's panel boundaries ? Suppose I create a child window with a button click:
<UserControl x:Class="ChildWindowTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Child="clr-namespace:ChildWindowTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" >
<Grid x:Name="LayoutRoot" >
<StackPanel Width="500" Height="500">
<Button Width="100" Height="25" Click="Button_Click" Content="Child Window"/>
</StackPanel>
</Grid>
</UserControl>
<controls:ChildWindow
x:Class="ChildWindowTest.ChildWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
Width="400" Height="300" Title="ChildWindow1" >
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
</Grid>
</controls:ChildWindow>
I can move the generated child to left, right and down off screen (clipped off). I want to avoid that, basically set up a boundary within which child window is allowed to be moved (within StackPanel boundary)
Thank you for any suggestion ..
I tried to keep my solution as simple as possible, but still it was quite challenging to achieve the desired behaviour.
Basically, if you leave the ChildWindow's ControlTemplate alone, the following code should work for you:
private void Button_Click(object sender, RoutedEventArgs e)
{
ChildWindow wnd = new ChildWindow();
wnd.Width = 800;
wnd.Height = 600;
wnd.Title = "Test";
wnd.MouseLeftButtonUp += wnd_MouseLeftButtonUp;
wnd.Loaded += wnd_Loaded;
wnd.Show();
}
private void wnd_Loaded(object sender, RoutedEventArgs e)
{
var wnd = sender as ChildWindow;
myApplicationActualWidth = Application.Current.Host.Content.ActualWidth;
myApplicationActualHeight = Application.Current.Host.Content.ActualHeight;
//This call might be necessary to make sure the visual tree of wnd is constructed and can be inspected
wnd.UpdateLayout();
//wnd is guaranteed to have at least one child here
myRoot = (FrameworkElement)VisualTreeHelper.GetChild(wnd, 0);
myContentRoot = myRoot.FindName("ContentRoot") as FrameworkElement;
//this is the title bar part
myChrome = myRoot.FindName("Chrome") as FrameworkElement;
myChrome.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(wnd_MouseLeftButtonUp), true);
myTransform = (myContentRoot.RenderTransform as TransformGroup).Children.OfType<TranslateTransform>().First();
}
private void wnd_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point rootMousePosition = e.GetPosition(sender as ChildWindow);
Point contentRootMousePosition = e.GetPosition(myContentRoot);
Point currentOffset = new Point(rootMousePosition.X - contentRootMousePosition.X, rootMousePosition.Y - contentRootMousePosition.Y);
TransformChildWindowToValidPosition(currentOffset);
}
private void TransformChildWindowToValidPosition(Point currentPosition)
{
// handle left side
if (currentPosition.X < 0)
{
myTransform.X = myTransform.X - currentPosition.X;
}
// handle top
if (currentPosition.Y < 0)
{
myTransform.Y = myTransform.Y - currentPosition.Y;
}
// handle right side
if (currentPosition.X + myContentRoot.ActualWidth > ActualWidth)
{
myTransform.X = myTransform.X - (currentPosition.X + myContentRoot.ActualWidth - myApplicationActualWidth);
}
// handle bottom
if (currentPosition.Y + myContentRoot.ActualHeight > ActualHeight)
{
myTransform.Y = myTransform.Y - (currentPosition.Y + myContentRoot.ActualHeight - myApplicationActualHeight);
}
}
private TranslateTransform myTransform;
private FrameworkElement myRoot;
private FrameworkElement myContentRoot;
private FrameworkElement myChrome;
private double myApplicationActualWidth;
private double myApplicationActualHeight;
This code basically does not allow you the move your ChildWindow outside the boundaries of the current SL application, but it should be a piece of cake to get your hands on the dimensions of the "parent" control of the window and readjust the values for all the edges.
Hope I could help...
Related
I have an ellipse in the code below which has a mouse move event. Now everything is good unless I use mouse.capture on the element.
The mouse move event gets fired immediately I apply mouse.capture on the element even if I don't move my mouse, and also moving the position of the element by using setleft property also fires the mouse move event even if I don't move my mouse at all! Is this supposed to happen? And if there's a solution I badly need it..
The code below will generate the exact problem...
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Test
{
public partial class MainWindow : Window
{
public Ellipse elp;
public Point clickPoint;
public MainWindow()
{
InitializeComponent();
testcanv.Background = Brushes.Transparent;
}
private void down(object sender, MouseButtonEventArgs e)
{
clickPoint = e.GetPosition(testcanv);
if (e.ChangedButton == MouseButton.Left)
{
elp = new Ellipse
{
Stroke = Brushes.Blue,
StrokeThickness = 2,
Width = 200,
Height = 200,
Margin = new Thickness(-250)
};
Canvas.SetLeft(elp, clickPoint.X);
Canvas.SetTop(elp, clickPoint.Y);
elp.MouseMove += circle_move;
testcanv.Children.Add(elp);
}
}
private void circle_move(object sender, MouseEventArgs e)
{
text.Text += "Moved,";
}
private async void click(object sender, RoutedEventArgs e)
{
Mouse.Capture(elp, CaptureMode.Element);
await Task.Delay(1000);
Canvas.SetLeft(elp, 100);
Canvas.SetTop(elp, 100);
await Task.Delay(500);
Mouse.Capture(null);
}
}
}
And here is my XAML
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:balchal"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Canvas x:Name="testcanv" Grid.Column="0" Grid.Row="0" MouseDown="down"/>
<Button Grid.Column="1" Grid.Row="1" Content="Button" Click="click"/>
<TextBox x:Name="text" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Center" Width="300" Margin="10"/>
</Grid>
</Window>
Thanks in advance... I really appreciate any kind of help...
I may be wrong, but I believe that the MouseMove event always fires when capturing
The simplest solution, if it's happening consistently, is probably just to store the state in the click event and check for it at the start of the move event: returning immediately and resetting the var.
bool CapturedFlag = false;
private void MoveEvent(object sender, MouseButtonEventArgs e)
{
if(CapturedFlag = true)
{
CapturedFlag = false;
return;
}
// other code
}
private async void CaptureEvent(object sender, RoutedEventArgs e)
{
CapturedFlag = true;
// other code
}
I have a Window with a ScrollViewer and inside the ScrollViewer there is a Rectangle. Now I added code to drag the Rectangle which works fine. But I have no idea how to show the Scrollbars when the Rectangle is moved outside the view. I thought this will happen automatically which is not the case?
Here is my XAML:
<Window x:Class="WpfApp4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp4"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<ScrollViewer Name="_scrollViewer" CanContentScroll="True"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Rectangle Name="_myRect" Width="100" Height="100" Fill="Blue"/>
</ScrollViewer>
</Window>
And the code behind:
public partial class MainWindow : Window
{
private Point _origin;
private Point _start;
private ScaleTransform _scaleTransform = new ScaleTransform();
private TranslateTransform _translateTransform = new TranslateTransform();
public MainWindow()
{
InitializeComponent();
var group = new TransformGroup();
group.Children.Add(_scaleTransform);
group.Children.Add(_translateTransform);
_myRect.RenderTransform = group;
// Hook up events
_myRect.MouseLeftButtonDown += _myRect_MouseLeftButtonDown;
_myRect.MouseLeftButtonUp += _myRect_MouseLeftButtonUp;
_myRect.MouseMove += _myRect_MouseMove;
}
private void _myRect_MouseMove(object sender, MouseEventArgs e)
{
if (_myRect.IsMouseCaptured)
{
Vector v = _start - e.GetPosition(this);
_translateTransform.X = _origin.X - v.X;
_translateTransform.Y = _origin.Y - v.Y;
}
}
private void _myRect_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_myRect.ReleaseMouseCapture();
this.Cursor = Cursors.Arrow;
}
private void _myRect_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_start = e.GetPosition(this);
_origin = new Point(_translateTransform.X, _translateTransform.Y);
Cursor = Cursors.Hand;
_myRect.CaptureMouse();
}
}
[UPDATED]:
So based on the input I got I change XAML and the code behind to the following - but still no scrollbars?
<Window x:Class="WpfApp4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp4"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<ScrollViewer Name="_scrollViewer" CanContentScroll="True"
HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Canvas Name="_myCanvas">
<Rectangle Name="_myRect" Width="100" Height="100" Fill="Blue" Canvas.Left="305" Canvas.Top="129"/>
</Canvas>
</ScrollViewer>
</Window>
Code Behind:
public partial class MainWindow : Window
{
private Point _start;
public MainWindow()
{
InitializeComponent();
// Hook up events
_myRect.MouseLeftButtonDown += _myRect_MouseLeftButtonDown;
_myRect.MouseLeftButtonUp += _myRect_MouseLeftButtonUp;
_myRect.MouseMove += _myRect_MouseMove;
}
private void _myRect_MouseMove(object sender, MouseEventArgs e)
{
if (_myRect.IsMouseCaptured)
{
var canvasRelativePosition = e.GetPosition(_myCanvas);
Debug.WriteLine($"New Position: {canvasRelativePosition}");
Canvas.SetTop(_myRect, canvasRelativePosition.Y - _start.Y);
Canvas.SetLeft(_myRect, canvasRelativePosition.X - _start.X);
}
}
private void _myRect_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_myRect.ReleaseMouseCapture();
this.Cursor = Cursors.Arrow;
}
private void _myRect_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_start = e.GetPosition(_myRect);
Debug.WriteLine($"Start Position: {_start}");
Cursor = Cursors.Hand;
_myRect.CaptureMouse();
}
}
If you want your transform to affect layout, you have to use LayoutTransofrm. RenderTransform only changes apperance.
Any transformations associated with an elements LayoutTransform
property will have an impact on the subsequent Measure and Arrange
steps. Whereas a RenderTransform will not have any impact on the
layout process and will only effect rendering.
Read more here.
However, LayoutTransform ignores TranslateTransform
LayoutTransform ignores TranslateTransform operations. This is because
the layout system behavior for child elements of a FrameworkElement
auto-corrects any offsets to the position of a scaled or rotated
element into the layout and coordinate system of the parent element.
Read more here
All that means, to achieve moving your element, you can not use Transforms. You could try to change position of your element by hand (Margin, Canvas.Left/Right or other ideas).
I have a control which i'm putting in dialog as a content. Due to relization of this dialog i have to create it every time when i need it(Show/Hide won't do the trick). I want my control to remember field content beetween calls. While i can apply viewmodel to achieve this i prefer just keep control as a field and assing it as content of dialog every time i need it. But i run into following error:
"Specified element is already the logical child of another element. Disconnect it first."
I tried to assing null to dialog window's content before closing it, but it doesn't solve the problem. Is there anything i can do?
Setting window.Content = null works fine for me. Following is the code I used:
public partial class MainWindow : Window
{
TextBlock textBlock = new TextBlock();
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
TestWindow testWindow = new TestWindow();
testWindow.Content = textBlock;
testWindow.Closing += HandleTestWindowClosing;
testWindow.Show();
}
void HandleTestWindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
var testWindow = sender as TestWindow;
if(testWindow!=null)
{
testWindow.Content = null;
testWindow.Closing -= HandleTestWindowClosing;
}
}
}
Check out the following working example. It isn't exactly your scenario, but pretty close. The key is setting the 'Child' property to null. It moves the TextBox from the top border to the bottom.
<Window x:Class="SO.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>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0" Click="Move_Click">Move</Button>
<Border x:Name="topBorder" Grid.Row="1">
<TextBlock x:Name="ctrl">Some Text Block</TextBlock>
</Border>
<Border x:Name="bottomBorder" Grid.Row="2"/>
</Grid>
</Window>
and the code behind:
using System.Windows;
namespace SO
{
public partial class MainWindow :Window
{
public MainWindow( )
{
InitializeComponent( );
}
private void Move_Click( object sender, RoutedEventArgs e )
{
this.topBorder.Child = null;
this.bottomBorder.Child = this.ctrl;
}
}
}
Is it possible to create a “resilient” line using C# in a wpf which can be manipulated by the mouse while their starting and ending points remain stable? In particular I would like it to be resizable and its curve to change its direction following the move of the cursor which has previously captured the line? It is something you meet very often in graphical interfaces of desktop applications.
Is the manipulationDelta Class suitable for that? I want something like what is described in http://msdn.microsoft.com/en-us/library/ee649090.aspx but with mouse manipulation, not touch.
Yes it is very possible.
In a real application I would probably use some custom controls for the thumbs to handle the positioning & mouse capturing pieces so they're reusuable.
Here's a basic example of something like this.
Here's the XAML
<Window x:Class="WpfApplication2.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">
<Canvas Name="myCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Line Name="myLine" X1="100" Y1="100" X2="200" Y2="200" Stroke="Black" />
<Ellipse Name="thumb1" Fill="Blue" Width="10" Height="10" Canvas.Left="95" Canvas.Top="95" Cursor="Hand" MouseLeftButtonDown="OnPoint1Down" MouseLeftButtonUp="OnPoint1Up" MouseMove="OnPoint1Move" />
</Canvas>
</Window>
And here's the code behind
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void OnPoint1Down( object sender, MouseButtonEventArgs e ) {
Mouse.Capture( thumb1 );
}
private void OnPoint1Up( object sender, MouseButtonEventArgs e ) {
Mouse.Capture( null );
}
private void OnPoint1Move( object sender, MouseEventArgs e ) {
if ( e.LeftButton == MouseButtonState.Pressed ) {
var point = e.GetPosition( myCanvas );
myLine.X1 = point.X;
myLine.Y1 = point.Y;
Canvas.SetLeft( thumb1, point.X - thumb1.Width/2 );
Canvas.SetTop( thumb1, point.Y - thumb1.Height/2 );
}
}
}
Is it possible to move a Tooltip or something like that with cursor when mouse goes on a specific control?
I tried TextBlock, but Margin property not work.
private TextBlock tooltip = new TextBlock();
private void imgRoom_MouseEnter(object sender, MouseEventArgs e)
{
Point position = e.GetPosition((IInputElement)sender);
tooltip.Visibility = System.Windows.Visibility.Visible;
tooltip.Margin = new Thickness(position.X, position.Y, 0, 0);
tooltip.Width = 100;
tooltip.Height = 100;
tooltip.Background = new SolidColorBrush(Colors.Red);
}
private void imgRoom_MouseMove(object sender, MouseEventArgs e)
{
Point position = e.GetPosition((IInputElement)sender);
tooltip.Margin = new Thickness(position.X, position.Y, 0, 0);
}
You can achieve the effect using a Popup and some simple properties upon it. From window code...
<Window x:Class="WpfApplication3.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>
<Rectangle Name="rect" Margin="50,50,0,0" Width="100" Height="100" Fill="LightBlue" MouseMove="Rectangle_MouseMove" MouseLeave="Rectangle_MouseLeave" />
<Popup Name="floatingTip" AllowsTransparency="True" Placement="Relative" PlacementTarget="{Binding ElementName=rect}">
<TextBlock>Look At Me</TextBlock>
</Popup>
</Grid>
</Window>
And this is what the codebehind would look like.
...
private void Rectangle_MouseMove(object sender, MouseEventArgs e)
{
if (!floatingTip.IsOpen) { floatingTip.IsOpen = true; }
Point currentPos = e.GetPosition(rect);
// The + 20 part is so your mouse pointer doesn't overlap.
floatingTip.HorizontalOffset = currentPos.X + 20;
floatingTip.VerticalOffset = currentPos.Y;
}
private void Rectangle_MouseLeave(object sender, MouseEventArgs e)
{
floatingTip.IsOpen = false;
}
...
So from the XAML you can see that the popup placement is relative to the rectangle. When you go mousing over the rectangle, it becomes visible, and its position is updated as the mouse moves. Naturally this is a very basic solution, but with some minor tweaks, handling events like 'MouseEnter' and property adjustment you can come up with some really neat effects.
I don't know if this is a best practice, or if it performs well, but you could use an Adorner.
I've created a proof of concept before, but haven't used it in a production scenario (yet). The adorner can be used to create something like this (tooltip), or a custom mouse cursor or drop target icons.
Make sure you set IsHitTestVisible = false if the adorner doesn't need to be hit tested and might appear directly under the mouse location.
UPDATE
Just fully read the description of adorners:
Common applications for adorners include:
Adding functional handles to a UIElement that enable a user to manipulate the element in some way (resize, rotate, reposition, etc.).
Provide visual feedback to indicate various states, or in response to various events.
Overlay visual decorations on a UIElement.
Visually mask or override part or all of a UIElement.
This moves the tooltip around with the mouse cursor.
private void OnMouseMoveHandler(object sender, MouseEventArgs args)
{
if ((sender as FrameworkElement).ToolTip == null)
(sender as FrameworkElement).ToolTip = new ToolTip() { Placement = PlacementMode.Relative };
double x = args.GetPosition((sender as FrameworkElement)).X;
double y = args.GetPosition((sender as FrameworkElement)).Y;
var tip = ((sender as FrameworkElement).ToolTip as ToolTip);
tip.Content = tooltip_text;
tip.HorizontalOffset = x + 10;
tip.VerticalOffset = y + 10;
}