Get the height/width of Window WPF - c#

I have the following code
<Window x:Class="Netspot.DigitalSignage.Client.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" WindowStyle="SingleBorderWindow"
WindowStartupLocation="CenterScreen"
WindowState="Normal" Closing="Window_Closing">
Any attempt to get the height / width return NaN or 0.0
Can anyone tell me a way of getting it ?
These 2 methods don't work
//Method1
var h = ((System.Windows.Controls.Panel)Application.Current.MainWindow.Content).ActualHeight;
var w = ((System.Windows.Controls.Panel)Application.Current.MainWindow.Content).ActualWidth;
//Method2
double dWidth = -1;
double dHeight = -1;
FrameworkElement pnlClient = this.Content as FrameworkElement;
if (pnlClient != null)
{
dWidth = pnlClient.ActualWidth;
dHeight = pnlClient.ActualWidth;
}
The application will not be running full screen.

1.) Subscribe to the window re size event in the code behind:
this.SizeChanged += OnWindowSizeChanged;
2.) Use the SizeChangedEventArgs object 'e' to get the sizes you need:
protected void OnWindowSizeChanged(object sender, SizeChangedEventArgs e)
{
double newWindowHeight = e.NewSize.Height;
double newWindowWidth = e.NewSize.Width;
double prevWindowHeight = e.PreviousSize.Height;
double prevWindowWidth = e.PreviousSize.Width;
}
Keep in mind this is very general case, you MAY (you may not either) have to do some checking to make sure you have size values of 0.
I used this to resize a list box dynamically as the main window changes. Essentially all I wanted was this control's height to change the same amount the window's height changes so its parent 'panel' looks consistent throughout any window changes.
Here is the code for that, more specific example:
NOTE I have a private instance integer called 'resizeMode' that is set to 0 in the constructor the Window code behind.
Here is the OnWindowSizeChanged event handler:
protected void OnWindowSizeChanged (object sender, SizeChangedEventArgs e)
{
if (e.PreviousSize.Height != 0)
{
if (e.HeightChanged)
{
double heightChange = e.NewSize.Height - e.PreviousSize.Height;
if (lbxUninspectedPrints.Height + heightChange > 0)
{
lbxUninspectedPrints.Height = lbxUninspectedPrints.Height + heightChange;
}
}
}
prevHeight = e.PreviousSize.Height;
}

You can get the width and height that the window was meant to be in the constructor after InitializeComponent has been run, they won't return NaN then, the actual height and width will have to wait until the window has been displayed.
When WindowState == Normal You can do this one from Width / Height after IntializeComponent().
When WindowState == Maximized You could get the screen resolution for this one with
System.Windows.SystemParameters.PrimaryScreenHeight;
System.Windows.SystemParameters.PrimaryScreenWidth;

You have to try to get the ActualWidth/ActualHeight values once the window is Loaded in the UI. Doing it in Window_Loaded works well.

XAML
<Grid x:Name="Grid1">
<Image x:Name="Back_Image" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</Grid>
CS
MainWindow() after InitializeComponent();
Back_Image.Width = Grid1.Width;
Back_Image.Height = Grid1.Height;

WPF does the creation of controls and windows in a deferred manner. So until the window is displayed for the first time, it might not have gone through layouting yet, thus no ActualWidth/ActualHeight. You can wait until the window is loaded and get the properties then, or yet better bind these properties to a target where you need them. You could also force the layouting via UpdateLayout().
Just want to add: try to minimize the amount of size dependant logic, it is almost always possible to avoid it. Unless you are writing a layout panel of course.

Notice that when you use controls with 100% of with, they have a NaN size till they are being represented
You can check the ActualHeigh or ActualWidth just when the Loaded event is fired, but never try to verify it before the windows controls are created. Forget the idea to control that in the constructor.
In my oppinion, the best place to control this kind of things is The SizeChanged event

Hi you can get the height of the current screen in WPF with
System.Windows.SystemParameters.PrimaryScreenHeight

I just add a variable name to my window and then get the width or height of the window i want from its width or height property respectifly.
XAML:
<Window x:Name="exampleName"
...
</Window>
C#:
exampleName.Height;
or:
exampleName.Width;

My solution in case of MVVM...
I have a ViewModelBase, which is the base class for all ViewModel of the app.
1.) I created in the ViewModelBase a virtual method, which could be overriten in the child ViewModels.
public virtual void OnWindowSizeChanged(object sender, SizeChangedEventArgs e) {}
2.) The ctr of the MainWindow : Window class
public MainWindow(object dataContext)
{
InitializeComponent();
if (dataContext is ViewModelBase)
{
this.SizeChanged += ((ViewModelBase)dataContext).OnWindowSizeChanged;
DataContext = dataContext;
}
}
3.) Eg. in the MainViewModel, which is extends the ViewModelBase
public override void OnWindowSizeChanged(object sender, SizeChangedEventArgs e)
{
Console.WriteLine(e.NewSize.Height);
}

Related

Calculate TextBox size from text size without actual TextBox

In a UWP app, is it possible to use the size and font of some text to calculate how big a TextBox would need to be to hold that text without scrolling?
This could be useful in designing an overall layout -- depending on the size of the TextBox, one might switch to a different configuration.
I'm going to be doing all this in code, not in xaml, as my app is cross-platform, and its graphical layout brains are in the platform-independent part.
If knowing the size is not possible, even knowing either of its dimensions would be nice, if that is possible.
Try this:
public sealed partial class MainPage
{
private TextBox _textBox;
private TextBlock _textBlock;
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
_textBlock = new TextBlock
{
Margin = new Thickness(10000, 10000, 0, 0),
};
MainGrid.Children.Add(_textBlock);
_textBox = new TextBox
{
Width = _textBlock.ActualWidth + 64, //is for clear button space
Height = _textBlock.ActualHeight,
};
_textBox.TextChanged += _textBox_TextChanged;
MainGrid.Children.Add(_textBox);
}
private void _textBox_TextChanged(object sender, TextChangedEventArgs e)
{
_textBlock.Text = _textBox.Text;
_textBox.Width = _textBlock.ActualWidth + 64;
_textBox.Height = _textBlock.ActualHeight;
}
}
It is not the perfect solution but may fit.
You just create somewhere off the screen textBlock and follow its size.
XAML has only 1 Grid x:Name="MainGrid"

Windows 10 UWP - GridView Items With no DataContext when Not Visible

I have an issue with a GridView in a UWP application that I'm working on...
Items in the GridView load correctly, however items that are out of view (off the page and not visible) do not have a DataContext assigned, and no event ever fires when the DataContext is assigned. Various bindings do work as TextBlocks that are bound get updated, but the the normal event workflow and Loaded events get all strange.
<GridView Grid.Row="1" Name="SearchGrid" ItemsSource="{Binding SearchItems}" ItemClick="SearchGrid_ItemClick">
<GridView.ItemTemplate>
<DataTemplate>
<local:RsrItemGridViewItem />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The grids all show correctly, except, for being able to properly delay load some items because the DataContext isn't set at time of load (and a DataContextChanged event isn't fired when the context is updated).
Does anyone have any ideas how to get notified when the control becomes visible? This seems like a notification bug, or there is some binding thing that I'm missing.
Thank you!
Does anyone have any ideas how to get notified when the control becomes visible?
You can't use FrameworkElement.Loaded event here to get notify when your RsrItemGridViewItem becomes visible, this event occurs when a FrameworkElement has been constructed and added to the object tree, and is ready for interaction.
GirdView control implements UI virtualization for better UI performance, if your GridView is bound to a collection of many items, it might download only items 1-50, When the user scrolls near the end of the list, then items 51 – 100 are downloaded and so on. But for example, there are only 20 items now be shown, but it might have loaded 45 items, 25 items could not be seen in this moment.
If you change the default ItemsPanel of GridView which is ItemsWrapGrid to for example VariableSizedWrapGrid, GridView will lose virtualization, and all items will be loaded at the same time even most of them can not be seen at one moment.
For you problem, I think what you can give a try is calculating the ScrollViewer's VerticalOffset with your GridView's height and the items's count be shown, and then you can know which items are been shown at this moment.
For example here:
private ObservableCollection<MyList> list = new ObservableCollection<MyList>();
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private double viewheight;
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var scrollViewer = FindChildOfType<ScrollViewer>(gridView);
scrollViewer.ViewChanged += ScrollViewer_ViewChanged;
viewheight = gridView.ActualHeight;
}
private void ScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
var scrollViewer = sender as ScrollViewer;
var Y = scrollViewer.VerticalOffset;
//calculate here to get the displayed items.
}
public static T FindChildOfType<T>(DependencyObject root) where T : class
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++)
{
var child = VisualTreeHelper.GetChild(current, i);
var typedChild = child as T;
if (typedChild != null)
{
return typedChild;
}
queue.Enqueue(child);
}
}
return null;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
list.Clear();
for (int i = 0; i < 200; i++)
{
list.Add(new MyList { text = "Item " + i });
}
}
Since GridView control's layout is adaptive to the app's size, the current displayed count is dynamic, you can try other height based properties (for example each item's height) and the ScrollViewer's VerticalOffset to calculate, there is no ready-made method to get your work done, it's a little complex to calculate, but I think there is no better solution for now.
After doing some testing with this, what I found out worked (though it's not very clean, and I believe there is a bug with bindings) was to add the custom control to the GridView, then in the grid view adding a DataContext={Binding} to the Image I wanted to get notified of an update on.
<UserControl ...><Image DataContext="{Binding}" DataContextChanged="ItemImage_DataContextChanged" /></UserControl>
The main control doesn't get notified of a DataContext change, but the child elements are notified.

Why do the labels on my custom user control cause the mouseleave event to fire?

I have written a custom WPF UserControl. It's a square with a Grid named Base. To that grid I add an ellipse and two labels (volume and location), which are populated with text pulled from the properties of an object which is given as a parameter upon control instantiation.
Here's the XAML:
<UserControl x:Class="EasyHyb.SampleWellControl"
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="100" d:DesignWidth="100">
<Grid x:Name="Base">
</Grid>
</UserControl>
And the constructor/event functions in the codebehind:
public SampleWellControl(int size, SourceSample sample)
{
InitializeComponent();
this.sample = sample;
this.Width = this.Height = size;
this.selected = SelectionStatus.Unselected;
double spacing = size / 4;
volume = new Label();
location = new Label();
volume.Content = String.Format("{0:0.00}", sample.volume);
location.Content = sample.well.well;
volume.HorizontalAlignment = location.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
volume.FontFamily = location.FontFamily = new System.Windows.Media.FontFamily("Meiryo UI");
volume.FontWeight = location.FontWeight = FontWeights.Bold;
volume.Background = location.Background = Base.Background = this.Background = Brushes.Transparent;
volume.Margin = new Thickness(0, spacing, 0, 0);
location.Margin = new Thickness(0, spacing * 2, 0, 0);
well = new Ellipse();
well.Width = well.Height = this.Width;
well.StrokeThickness = 3;
Base.Children.Add(well);
Base.Children.Add(volume);
Base.Children.Add(location);
this.MouseEnter += SampleWellControl_MouseEnter;
this.MouseLeave += SampleWellControl_MouseLeave;
this.MouseUp += SampleWellControl_MouseUp;
this.Cursor = Cursors.Hand;
UpdateFillAndStroke();
}
void SampleWellControl_MouseLeave(object sender, MouseEventArgs e)
{
RevertWell();
}
void SampleWellControl_MouseEnter(object sender, MouseEventArgs e)
{
HighlightWell();
}
public void HighlightWell()
{
if (this.selected == SelectionStatus.Pooled)
{
return;
}
if (this.selected == SelectionStatus.Unselected)
{
this.well.Stroke = this.strokes[SelectionStatus.Selected];
}
else
{
this.well.Stroke = this.strokes[SelectionStatus.Unselected];
}
}
public void RevertWell()
{
if (this.selected == SelectionStatus.Pooled)
{
return;
}
if (this.selected == SelectionStatus.Unselected)
{
this.well.Stroke = this.strokes[SelectionStatus.Unselected];
}
else
{
this.well.Stroke = this.strokes[SelectionStatus.Selected];
}
}
Basically, when the mouse enters the control, the stroke of the ellipse should change unless the well has undergone an operation to give it a "Pooled" status.
When the mouse enters the control, it responds exactly as I expect: the MouseEnter event handler fires. However, when a user moves the mouse over one of the labels inside the control, the MouseLeave event fires. So even though the label is ostensibly part of the control The pictures below show what I'm talking about. Print Screen removes the cursors, but I put blue dots to indicate where the cursor is:
Responding properly:
Now it seems to think the mouse has left the control:
I've tried adding MouseEnter and MouseLeave event handlers to the labels, but they don't fire. The cursor also changes from a hand to a pointer when the labels are moused over. I've tried adding MouseEnter and MouseLeave event handlers to the control after it's instantiated within another class. I added transparent backgrounds to the Grid, control, and labels, but that didn't make any difference either.
I also checked in my MouseLeave event handler to see if the mouse was over the control, and it seems that the control is not detecting the cursor as being over the control itself:
if(!this.IsMouseOver)
{
RevertWell();
}
//also tried IsMouseDirectlyOver
I would like MouseLeave to fire only when the cursor exits the square bounds of the control. How can I accomplish this while keeping the labels?
Use a combination of
IsHitTestVisible="False"
on all of your objects added to Base:
volume = new Label();
volume.IsHitTestVisible="False";
and then your container which has the events, give a background
<Grid x:Name="Base" Background="White">
(Also I wanted to comment but reputation is stupid)
Well shucks, after a lot of searching around it turns out the problem was indeed contained within another control. I had another UserControl class, EmptyWellControl, which had a Label. The text position within the label was calculated using the Label's Height property, which resulted in a nonsense value that made the label extend vertically well beyond the dimensions of the window. The label didn't have a background, but nevertheless interfered with the controls whose path it crossed. Since the empty and sample wells were all laid out on the same grid, every SampleWellControl was affected by these labels.

Make the size of a System.Windows.Forms.Control dependent on the size of the parent

I want to make some controls (specifically: Button, Label and Panel) to become dependent on the size of their parent. So a button might be 0.1 times the width of the parent, 0.05 the height, and be positioned in 0.3 times the width and 0.2 times the height.
Now I have 2 problems:
First I want to change the behaviour of the Control class into a sort of 'relative size and relative position'-Control. This would be very easy if the Control class had an 'onParentResized' method I could override, but it hasn't. So now my solution is this
class RelativeControl : Control
{
Control previousParent;
double relativeWidth, relativeHeight, relativeX, relativeY;
public RelativeControl(double RelativeWidth, double RelativeHeight, double RelativeX, double RelativeY)
{
// the arguments need to be between 0 and 1 normally, or the control is
// garanteed to be partially offscreen
this.relativeWidth = RelativeWidth;
this.relativeHeight = RelativeHeight;
this.relativeX = RelativeX;
this.relativeY = RelativeY;
}
protected override void OnParentChanged(EventArgs e)
{
if(previousParent != null)
{
previousParent.Resize -= new EventHandler(parentResized);
}
if(this.Parent != null)
{
this.Parent.Resize += parentResized;
}
this.previousParent = this.Parent;
}
private void parentResized(Object o, EventArgs e)
{
this.Width = (int)(this.Parent.Width * this.relativeWidth);
this.Height = (int)(this.Parent.Width * this.relativeHeight);
this.Location = new Point((int)(this.Parent.Width * this.relativeX), (int)(this.Parent.Height * this.relativeY));
}
}
Is this a good solution?
Second problem: I want to make the Button-class (as well as the Panel and Label class) to extend this new version of control. However this isn't possible as far as I know. My only option seems to be to make 3 classes and literally find-and-replace 'Control' by "Label", "Button" and "Panel" to get the result I want.
What should I be doing here?
I think you are after TableLayoutPanel control.
Happily on .NET platform do not have to worry anymore about child controls resizing or repositioning to the parent.
You have to make extensive use of the Dock and Anchor properties of the child controls.
You can start with the links but there are many tutorials about them on the web.
TableLayoutPanel
Anchor and Dock Child Controls
Create a Resizable Windows Form

Drag WPF Popup control

the WPF Popup control is nice, but somewhat limited in my opinion. is there a way to "drag" a popup around when it is opened (like with the DragMove() method of windows)?
can this be done without big problems or do i have to write a substitute for the popup class myself?
thanks
Here's a simple solution using a Thumb.
Subclass Popup in XAML and codebehind
Add a Thumb with width/height set to 0 (this could also be done in XAML)
Listen for MouseDown events on the Popup and raise the same event on the Thumb
Move popup on DragDelta
XAML:
<Popup x:Class="PopupTest.DraggablePopup" ...>
<Canvas x:Name="ContentCanvas">
</Canvas>
</Popup>
C#:
public partial class DraggablePopup : Popup
{
public DraggablePopup()
{
var thumb = new Thumb
{
Width = 0,
Height = 0,
};
ContentCanvas.Children.Add(thumb);
MouseDown += (sender, e) =>
{
thumb.RaiseEvent(e);
};
thumb.DragDelta += (sender, e) =>
{
HorizontalOffset += e.HorizontalChange;
VerticalOffset += e.VerticalChange;
};
}
}
There is no DragMove for PopUp. Just a small work around, there is lot of improvements you can add to this.
<Popup x:Name="pop" IsOpen="True" Height="200" Placement="AbsolutePoint" Width="200">
<Rectangle Stretch="Fill" Fill="Red"/>
</Popup>
In the code behind , add this mousemove event
pop.MouseMove += new MouseEventHandler(pop_MouseMove);
void pop_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
pop.PlacementRectangle = new Rect(new Point(e.GetPosition(this).X,
e.GetPosition(this).Y),new Point(200,200));
}
}
Building off of Jobi Joy's answer, I found a re-useable solution that allows you to add as a control within xaml of an existing control/page. Which was not possible adding as Xaml with a Name since it has a different scope.
[ContentProperty("Child")]
[DefaultEvent("Opened")]
[DefaultProperty("Child")]
[Localizability(LocalizationCategory.None)]
public class DraggablePopup : Popup
{
public DraggablePopup()
{
MouseDown += (sender, e) =>
{
Thumb.RaiseEvent(e);
};
Thumb.DragDelta += (sender, e) =>
{
HorizontalOffset += e.HorizontalChange;
VerticalOffset += e.VerticalChange;
};
}
/// <summary>
/// The original child added via Xaml
/// </summary>
public UIElement TrueChild { get; private set; }
public Thumb Thumb { get; private set; } = new Thumb
{
Width = 0,
Height = 0,
};
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
TrueChild = Child;
var surrogateChild = new StackPanel();
RemoveLogicalChild(TrueChild);
surrogateChild.Children.Add(Thumb);
surrogateChild.Children.Add(TrueChild);
AddLogicalChild(surrogateChild);
Child = surrogateChild;
}
}
Another way of achieving this is to set your Popup's placement to MousePoint. This makes the popup initially appear at the position of the mouse cursor.
Then you can either use a Thumb or MouseMove event to set the Popup's HorizontalOffset & VerticalOffset. These properties shift the Popup away from its original position as the user drags it.
Remember to reset HorizontalOffset and VerticalOffset back to zero for the next use of the popup!
The issue with loosing the mouse when moving too fast, could be resolved
This is taken from msdn:
The new window contains the Child content of Popup.
The Popup control maintains a reference to its Child content as a logical child. When the new window is created, the content of Popup becomes a visual child of the window and remains the logical child of Popup. Conversely, Popup remains the logical parent of its Child content.
In the other words, the child of the popup is displayed in standalone window.
So when trying to the following:
Popup.CaptureMouse() is capturing the wrapper window and not the popup itself. Instead using Popup.Child.CaptureMouse() captures the actual popup.
And all other events should be registered using Popup.Child.
Like Popup.Child.MouseMove, Popup.Child.LostCapture and so on
This has been tested and works perfectly fine
Contrary to what others have stated about this, I agree 100% with Jobi Joy's answer (which should honestly be the accepted answer). I saw a comment stating that the solution in the answer would cause memory fragmentation. This is not possible as creating new structs cannot cause memory fragmentation at all; in fact, using structs saves memory because they are stack-allocated. Furthermore, I think that this is actually the correct way to reposition a popup (after all, Microsoft added the PlacementRectangle property for a reason), so it is not a hack. Appending Thumbs and expecting a user to always place a Popup onto a canvas, however, is incredibly hacky and is not always a practical solution.
Private Point startPoint;
private void Window_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(null);
}
private void Window_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
Point relative = e.GetPosition(null);
Point AbsolutePos = new Point(relative.X + this.Left, relative.Y + this.Top);
this.Top = AbsolutePos.Y - startPoint.Y;
this.Left = AbsolutePos.X - startPoint.X;
}
}
This works for dragging my window, but like it was told if i move the mouse to fast, it would get out of window and stop raising the event. Without mentioning the dragging is not smooth at all. Does anyone knows how to do it properly, nice and smooth dragging, without loosing it when dragged too fast??? Post a simple example if possible, other than a whole tutorial that would get beginners like me lost in code. Thanks!

Categories

Resources