WPF - Maximizing borderless window by taking in account the user taskbar - wpf

I am creating a WPF window with a custom chrome, so I setted ResizeMode="NoResize" and WindowStyle="None" to implement my own chrome. However, there is an issue while maximizing the borderless window: it takes the whole screen.
I found the following trick to fix part of the issue:
http://chiafong6799.wordpress.com/2009/02/05/maximizing-a-borderlessno-caption-window/
This successfully restrain the window size to prevent from covering a taskbar. However, if the user have his taskbar positionned at the left or at the top, this won't work, as the window is at position 0,0.
Is there any way to retrieve more accurately the available area, or to query the user taskbar's position so I can position the maximized window accordingly?

I had a quick play around and it seems that setting the Windows Left and Top properties is ignored when setting WindowState.Maximized with a borderless form.
One workaround would be to ignore the WindowState functions and create your own Maximize/Restore functions
Rough example.
public partial class MainWindow : Window
{
private Rect _restoreLocation;
public MainWindow()
{
InitializeComponent();
}
private void MaximizeWindow()
{
_restoreLocation = new Rect { Width = Width, Height = Height, X = Left, Y = Top };
System.Windows.Forms.Screen currentScreen;
currentScreen = System.Windows.Forms.Screen.FromPoint(System.Windows.Forms.Cursor.Position);
Height = currentScreen.WorkingArea.Height;
Width = currentScreen.WorkingArea.Width;
Left = currentScreen.WorkingArea.X;
Top = currentScreen.WorkingArea.Y;
}
private void Restore()
{
Height = _restoreLocation.Height;
Width = _restoreLocation.Width;
Left = _restoreLocation.X;
Top = _restoreLocation.Y;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
MaximizeWindow();
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Restore();
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
DragMove();
}
base.OnMouseMove(e);
}
}
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" Height="74.608" Width="171.708" ResizeMode="NoResize" WindowStyle="None">
<Grid>
<Button Content="Max" HorizontalAlignment="Left" Margin="0,29,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click_1"/>
<Button Content="Restore" HorizontalAlignment="Left" Margin="80,29,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click_2"/>
</Grid>
</Window>
Obviously you will want to clean this code up, but it seems to work wherever the Taskbar is located, However you may need to add some logic to get the correct Left, Top if the users font DPI is larger than 100%

Another way to do this is by handling the WM_GETMINMAXINFO Win32 message. The code here shows how to do that.
Note that there are a few things that I would do differently, such as returning IntPtr.Zero instead of (System.IntPtr)0 in WindowProc and making MONITOR_DEFAULTTONEAREST a constant. But that's just coding style changes, and doesn't affect the net result.
Also make sure to pay attention to the update where the WindowProc is hooked during the SourceInitialized event instead of OnApplyTemplate. That's the better place to do it. If you're implementing a class derived from Window, then another option is to override OnSourceInitialized to hook the WindowProc instead of attaching to the event. That's what I normally do.

Related

How to automatically resize to content a Window with an Expander control?

I'm trying to figure out how to resize the Window of my application after an Expander control has been expanded or collapsed. It must grow or shrink. All the cases I've seen before on the StackOverflow suggest to set SizeToContent="WidthAndHeight". Is it a right workflow or should I change the size manually?
The application is consist of the MainWindow with an Expander-based user control in a StackPanel:
<Window x:Class="WpfAppBlend.MainWindow"
Title="MainWindow" Width="517" Height="298"
SizeToContent="Manual" ResizeMode="NoResize"
SizeChanged="Window_SizeChanged">
<StackPanel>
<local:UCForm/>
</StackPanel>
</Window>
This is my UserControl:
<UserControl x:Class="WpfAppBlend.UCForm"
d:DesignWidth="500" d:DesignHeight="320"
Width="500" Height="320">
<Grid>
<Expander ExpandDirection="Down" Header="Bookmarks"
Expanded="Expander_Expanded"
Collapsed="Expander_Collapsed">
<TextBlock>
Some text here...
</TextBlock>
</Expander>
</Grid>
</UserControl>
In my UserControl code-behind I have two empty event handlers:
public partial class UCForm : UserControl
{
private void Expander_Expanded(object sender, RoutedEventArgs e){
// How to get the instance of the Main Window here
// to change it's Height?
}
private void Expander_Collapsed(object sender, RoutedEventArgs e){
// How to get the instance of the Main Window here
// to change it's Height?
}
}
And my MainWindow class looks as follows:
public partial class MainWindow : Window
{
public MainWindow(){
InitializeComponent();
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e){
}
}
This is what I've got:
Setting SizeToContent="WidthAndHeight" doesn't work for me.
Expander expands but window size doesn't change.
UPDATED: The problem is solved now. The issue was at fixed height. So if you want to make SizeToContent to do all dirty work, — you should NEVER NEVER NEVER specify the Height of your UserControl (only Width) in the case of ExpandDirection="Down". When you specify a fixed Height of the UserControl you must to handle the size change by yourself within an Expanded() and Collapsed() handlers of your UserControl class. My application layout is fine now:
You can indeed use the SizeToContent="WidthAndHeight" option so that the Window automatically resizes to fit exactly its content.
However, to make it work properly, you need to remove the Width="500" Height="320" properties from your UCForm user control, as they give the control (and thus the window content) a fixed height. Your Window will then resize as the UserControl resizes.
As a side note:
private void Expander_Expanded(object sender, RoutedEventArgs e){
// How to get the instance of the Main Window here
// to change it's Height?
}
You almost NEVER want to get the instance of MainWindow and resize it from here. The key principle here is decoupling: make your UCForm re-usable by not forcing it to be used inside a MainWindow. What if one day you want to use your control somewhere else (another window, another app, or even outside WPF)?
If you want to change the size of UserControl by your own, here some approximate code for the handlers:
private void Expander_Expanded(object sender, RoutedEventArgs e)
{
// Getting current position of the Expander Control
Point exdCtrlPos = ((Expander)sender).TranslatePoint(new Point(0, 0), this);
// Y + Height of the Expander Control - Height of the UCForm Control
double delta = (exdCtrlPos.Y + ((Expander)sender).ActualHeight) - Height;
// Increasing the height of the UCForm by delta
Height += delta;
}
private void Expander_Collapsed(object sender, RoutedEventArgs e)
{
// Some code here
// Decreasing the height of the UCForm by delta
Height -= delta;
}

canvas draw shape on MouseMove lag

I have very simple scenario: there is a canvas and I need to draw a line on canvas using MouseMove. But when I move the mouse pointer, second line's point (which is set in mouse move) doesn't match current mouse position.
UPD 2:
Delta depends on speed of mouse, if speed is large - delta is large and noticeable(lag). I've noticed that this bug is more visible if you move your mouse not very fast and not very slow.
You can download sample project here.
Something like on the picture when mouse moves fast:
Some source code:
<Window x:Class="WpfApplication32.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Canvas x:Name="MainCanvas"
MouseLeftButtonDown="MainCanvas_OnMouseLeftButtonDown"
MouseMove="MainCanvas_OnMouseMove"
Background="White"
/>
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApplication32
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Line _currentLine;
private bool _isDrawing;
public MainWindow()
{
InitializeComponent();
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
MainCanvas.Focus();
}
private void MainCanvas_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (_isDrawing)
{
_currentLine = null;
_isDrawing = false;
return;
}
_isDrawing = true;
_currentLine = new Line(){Stroke = Brushes.Green};
var p = e.GetPosition(MainCanvas);
_currentLine.X1 = p.X;
_currentLine.Y1 = p.Y;
_currentLine.X2 = p.X;
_currentLine.Y2 = p.Y;
MainCanvas.Children.Add(_currentLine);
}
private void MainCanvas_OnMouseMove(object sender, MouseEventArgs e)
{
if (_currentLine == null)
return;
var p = e.GetPosition(MainCanvas);
_currentLine.X2 = p.X;
_currentLine.Y2 = p.Y;
}
}
}
I've tried to use CompositeTarget.Render, also timer to change second point every 20ms but it didn't help.
I have legacy project in which code depends a lot on this approach(canvas mouseMove and shapes). So I need easiest way to eliminate this lag or some ideas about a reason of this bug) Thanks.
UPD:
I've tried to record video with this problem but I'm not good at it. Here is some screen from my recorded to show the problem:
http://prntscr.com/64hueg
UPD 2:
I've tried to use OnRender of Window object to do the same without canvas. I've used DataContext to draw the line - same issue here. DataContext is considered faster than Canvas and Line (Shape). So this is not Canvas issue.
I've also tried to use WritableBitmap to draw the line - no difference.
I thought that there might be a problem with MouseMove event - I read if there is a lot of objects(not my case but still) MouseMove might fire with delays so I used Win32 WM_MOUSEMOVE but it didn't helped as well. In my case delay between MW_MOUSEMOVE and wpf MouseMove event was <1000 ticks.
The only answer I see so far is render delay. I don't know how to improve it because it is wpf internals =(.
By the way Paint.net seems to use wpf and this problem occurs there as well.
This cannot be fixed because this is due to WPF's internal render system. There will always be a lag even if visual tree is simple. Complex visual tree results to more delay. I've spend a lot of time trying solving this.
Try using MouseMove event not on main canvas but on Window itself.
I recently got same problem using MouseMove on Image and it was laggy as hell
Switching to window's event helped me a lot.
<Window x:Name="Window1" x:Class="WpfApp2.MViewer"
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:WpfApp2"
mc:Ignorable="d"
Title="MViewer" Height="454.411" Width="730.515" Loaded="Window_Loaded" Closing="Window1_Closing" ContentRendered="Window1_ContentRendered" MouseMove="Window1_MouseMove">
<Grid>
<Image x:Name="Image1" MouseMove="Image1_MouseMove"/>
<Line Name="Line1" Visibility="Visible" Stroke="Red" StrokeThickness="0.75" />
<Line Name="Line2" Visibility="Visible" Stroke="Red" StrokeThickness="0.75" />
</Grid>
</Window>
and
private void Window1_MouseMove(object sender, MouseEventArgs e)
{
Line1.Visibility = Visibility.Visible;
Line1.X1 = Mouse.GetPosition(this).X;
Line1.X2 = Mouse.GetPosition(this).X;
Line1.Y1 = 0;
Line1.Y2 = Window1.Height;
Line2.Visibility = Visibility.Visible;
Line2.X1 = 0;
Line2.X2 = Window1.Width;
Line2.Y1 = Mouse.GetPosition(this).Y;
Line2.Y2 = Mouse.GetPosition(this).Y;
}

Removing a UserControl added at runtime using a button within UserControl

I have seen a few posts addressing how to remove an UserControl that has been added during runtime, but my problem is a little different. I have a UserControl that consists of an image with a small "x" button on the top right corner that is used to remove itself (the UserControl) from its parent canvas. Also to note is that the UserControl is added during runtime when the user doubleclicks on a ListboxItem. I have a Click event handler for the top right corner button but this code is not running at all. I know this because I have a breakpoint in this code which is not reached when I click the button.
So,
Why isn't the click event of the remove button being handled?
Maybe there is a better way to implement this. Please advise.
Here's the code used for adding it:
private void MyListBox_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.OriginalSource.ToString() == "System.Windows.Controls.Border" || e.OriginalSource.ToString() == "System.Windows.Controls.Image" || e.OriginalSource.ToString() == "System.Windows.Controls.TextBlock")
{
Expression.Blend.SampleData.MyCollection.Dataset lbi = ((sender as ListBox).SelectedItem as Expression.Blend.SampleData.MyCollection.Dataset);
var new_usercontrol = new MyUserControl();
new_usercontrol.MyImageSourceProperty = lbi.Image;
MyCanvas.Children.Add(new_usercontrol);
Canvas.SetLeft(new_usercontrol, 100);
Canvas.SetTop(new_usercontrol, 100);
Canvas.SetZIndex(new_usercontrol, 100);
}
}
The following is the cs code for the UserControl:
public partial class ModuleElement : UserControl
{
public ImageSource MyProperty
{
get { return (ImageSource)this.image.Source; }
set { this.image.Source = value; }
}
public ModuleElement()
{
this.InitializeComponent();
}
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
((Canvas)this.Parent).Children.Remove(this);
}
}
The XAML:
<Grid x:Name="LayoutRoot">
<Image x:Name="image" />
<Button x:Name="RemoveButton" Content="X" HorizontalAlignment="Right" Height="17.834" Margin="0" VerticalAlignment="Top" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Click="RemoveButton_Click">
</Button>
</Grid>
Thanks in advance,
Bryan
So I tried your code here exactly except for some name changes and could not reproduce your issue. In my personal experience your issue here has to be that for some reason the event for the click isn't subscribed to properly. For this I would go into designer for the user control, wipe out the current event for the button and double click in the designer event textbox such that VS or Blend generates all the code necessary for a proper subscription.
I have created a sample based on your code here. Feel free to pull it down and take a look to see if you can find any inconsistencies.
As far as a better way to implement this, check out the good old MVVM pattern and the MVVM Light Toolkit. With this you can have a central ViewModel class that will handle all of your button commands and binding without code behind.

WPF FrameworkElement not receiving Mouse input

Trying to get OnMouse events appearing in a child FrameworkElement. The parent element is a Panel (and the Background property is not Null).
class MyFrameworkElement : FrameworkElement
{
protected override void OnMouseDown(MouseButtonEventArgs e)
{
// Trying to get here!
base.OnMouseDown(e);
}
}
public class MyPanel : Panel
{
protected override void OnMouseDown(MouseButtonEventArgs e)
{
// This is OK
base.OnMouseDown(e);
}
}
OnMouse never gets called, event is always unhandled and Snoop tells me that the routed event only ever seems to get as far as the Panel element.
<Window
x:Class="WpfApplication5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication5"
Title="Window1" Height="300" Width="300">
<Border x:Name="myBorder" Background="Red">
<l:MyPanel x:Name="myPanel" Background="Transparent">
<l:MyFrameworkElement x:Name="myFE"/>
</l:MyPanel>
</Border>
</Window>
Docs say that FrameworkElement handles Input, but why not in this scenario?
OnMouseDown will only be called if your element responds to Hit Testing. See Hit Testing in the Visual Layer. The default implementation will do hit testing against the graphics drawn in OnRender. Creating a Panel with a Transparent background works because Panel draws a rectangle over its entire area, and that rectangle will catch the hit test. You can get the same effect by overriding OnRender to draw a transparent rectangle:
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawRectangle(Brushes.Transparent, null,
new Rect(0, 0, RenderSize.Width, RenderSize.Height));
}
You could also override HitTestCore so that all clicks are counted as hits:
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
I was able to reproduce the scenario you described. I did some playing around, and it wasn't until I changed the base class of MyFrameworkElement from FrameworkElement to something more concrete, like UserControl that events started firing like they should. I'm not 100% sure why this would be, but I would recommend using one of the classes derived from FrameworkElement that would suit your needs (like Panel, as you did in the example above, or Button).
I'd be curious to know the exact reason your example above produces these results...

Cant drag and move a WPF Form

I design a WPF form with Window Style=None. So I Cannot see the drag bar in the form. How can I move the Form with WindowStyle=None Property?
I am using a main window to hold pages (creating a navigation style program), and in the code behind of my main window, I inserted this...
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
// Begin dragging the window
this.DragMove();
}
... and it works like a charm. This is with windowstyle=none. Its nice in the sense that you can click anywhere on the app and move it instead of just being limited to a top bar.
See this question.
Basically you use the Window.DragMove method for this.
In our application we have Windows with WindowStyle set to "none", we implemented the functionality to drag the Window, but only from the header rather than from any point in the Window. We did this by adding a Border as a header, then adding a Thumb to fill the entire Border. We then handle the DragDelta method on the Thumb in the code-behind for the Window.
<Border
Name="headerBorder"
Width="Auto"
Height="50"
VerticalAlignment="Top"
CornerRadius="5,5,0,0"
DockPanel.Dock="Top"
Background="{StaticResource BackgroundBrush}"
BorderThickness="1,1,1,1"
BorderBrush="{StaticResource BorderBrush}">
<Grid>
<Thumb
x:Name="headerThumb"
Opacity="0"
Background="{x:Null}"
Foreground="{x:Null}"
DragDelta="headerThumb_DragDelta"/>
</Grid>
</Border>
Then in the code-behind we have the following event handler...
private void headerThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Left = Left + e.HorizontalChange;
Top = Top + e.VerticalChange;
}
I don't know if this is any better than the other method, it's just the way we did it.
either inside the windows on load function or inside the grid's on load function use a deligate to trigger the DragMove() method on Mouse Click
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
this.MouseLeftButtonDown += delegate{DragMove();};
}
If you simply add this.DragMove(); and you are using Bing Maps, then you will get some frustrating behavior when trying to pan the map.
Using TabbyCool's answer was a good solution however, you can not drag the window against the top of the screen to maximise it.
My solution was just checking that the position.Y of my click relative to my top bar grid was less than a suitable amount.
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Point pt = e.GetPosition(topBar);
Debug.WriteLine(pt.Y);
if (pt.Y < topBar.ActualHeight)
{
DragMove();
}
}

Resources