How to draw WPF Adorners on top of everything else? - wpf

I've added an Adorner to my DateTimePicker control but it's not shown on top of the other controls. Why? How do I fix it?
My XAML currently goes like this:
<UserControl x:Class="IntelliMap.WPF.DateTimePicker"
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"
xmlns:wpftc="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
mc:Ignorable="d">
...
<AdornerDecorator>
<Grid>
...
<TextBox x:Name="DateDisplay"
HorizontalAlignment="Stretch" ...>
</TextBox>
...
</Grid>
</AdornerDecorator>
</UserControl>
The adorner itself is a separate class from the UserControl and added in the constructor:
public DateTimePicker()
{
InitializeComponent();
...
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(DateDisplay);
if (adornerLayer != null)
{
adornerLayer.Add(_upDownBtns = new TextBoxUpDownAdorner(DateDisplay));
_upDownBtns.Click += (textBox, direction) => { OnUpDown(direction); };
}
}

The problem apparently occurs because the Adorners governed by the AdornerDecorator are only guaranteed to appear on top of the controls inside the AdornerDecorator. It is necessary to wrap most of the contents of the window in an AdornerDecorator instead, but after doing this, AdornerLayer.GetAdornerLayer() apprently can't see the AdornerDecorator under some circumstances and returns null.
The documentation claims "GetAdornerLayer walks up the visual tree, starting at the specified UIElement, and returns the first adorner layer it finds." In reality, GetAdornerLayer cannot find an AdornerDecorator located outside of the UserControl, at least not in .NET 3.5. I fixed the problem by doing exactly what GetAdornerLayer claims to do itself:
static AdornerLayer GetAdornerLayer(FrameworkElement subject)
{
AdornerLayer layer = null;
do {
if ((layer = AdornerLayer.GetAdornerLayer(subject)) != null)
break;
} while ((subject = subject.Parent as FrameworkElement) != null);
return layer;
}
public DateTimePicker()
{
InitializeComponent();
...
this.Loaded += (s, e) =>
{
// not null anymore!
AdornerLayer adLayer = GetAdornerLayer(DateDisplay);
};
}
Finally, GetAdornerLayer must be called from the Loaded event instead of the constructor.

There's already an adorner layer in the default Window style, and that adorner layer sits above the content of the window.
So just remove the AdornerLayer from the UserControl and that should work.

Related

WPF Window: Need to use SizeToContent up to a specific Size

I have a WPF window with content that can vary it's size (a DataGrid).
SizeToContent works great for small Datasets, but for large Datasets the window gets too big on initial display.
How would I use SizeToContent in conjuction with a Maximum initial size of 600x600 (the window should still be able to be resized larger manually or maximized). Also the window can be hidden and reshown multiple times and should retain it's size when reshown (that is already the default behavior I'd like to retain).
myWindow.SizeToContent = SizeToContent.WidthAndHeight;
It sounds like what you want is to temporarily set the MaxWidth and MaxHeight on first render, but then turn it off after the first render. The problem is, if you turn off those values after the first render, then your SizeToContent will kick in and the window will resize to the large Dataset again!
To alleviate this, you will also have to turn off your SizeToContent after the first render. Here's a solution that worked for me:
MainWindow.xaml:
<Window x:Class="_72120715.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:_72120715"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" SizeToContent="WidthAndHeight" MaxWidth="600" MaxHeight="600">
<Grid>
<ListBox ItemsSource="{Binding Stuff}"/>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public ObservableCollection<string> Stuff { get; set; }
public MainWindow()
{
InitializeComponent();
ContentRendered += MainWindow_ContentRendered;
DataContext = this;
Stuff = new ObservableCollection<string>();
for (int i = 0; i < 100; i++)
{
Stuff.Add($"This is stuff {i}");
}
}
private void MainWindow_ContentRendered(object sender, EventArgs e)
{
// Clear the MaxWidth/MaxHeight so that you can manually resize larger
MaxWidth = double.PositiveInfinity;
MaxHeight = double.PositiveInfinity;
// Clear the SizeToContent so that it doesn't automatically resize to large datasets
SizeToContent = SizeToContent.Manual;
}
}

Saving FrameworkElement with its DataContext to image file does no succeed

I have a simple UserControl called UserControl1 that contains a TextBlock:
<UserControl x:Class="WpfApplication2.UserControl1"
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">
<Grid>
<TextBlock Text="{Binding}"/>
</Grid>
</UserControl>
I initialized a new instance of it and gave it a DataContext in code. when the window is closing I have to draw this control to an image file.
The UserControl does not render the bounded text in the file that been created.
and this is my code using the usercontrol:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Closing += MainWindow_Closing;
}
void MainWindow_Closing(object sender, CancelEventArgs e)
{
UserControl1 uc = new UserControl1();
uc.DataContext = "hello";
uc.Height = 100;
uc.Width = 100;
uc.Background = Brushes.LightBlue;
DrawToImage(uc);
}
private void DrawToImage(FrameworkElement element)
{
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
element.Arrange(new Rect(element.DesiredSize));
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)element.Width, (int)element.Height,
120.0, 120.0, PixelFormats.Pbgra32);
bitmap.Render(element);
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (Stream s = File.OpenWrite(#"C:\555.png"))
{
encoder.Save(s);
}
}
}
I Hope It's clear enough, any help will be very appreciated.
You just forgot to force a Layout update on your control after manually Measuring/Arrangeing it (which will not be enough to force binding resolving).
A simple call to UpdateLayout makes it work :
private void DrawToImage(FrameworkElement element)
{
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
element.Arrange(new Rect(element.DesiredSize));
element.UpdateLayout();
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)element.Width, (int)element.Height,
120.0, 120.0, PixelFormats.Pbgra32);
bitmap.Render(element);
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (Stream s = File.OpenWrite(#"C:\555.png"))
{
encoder.Save(s);
}
}
Edit : More on when bindings are resolved : link
Try to call the function SaveImage() on userControl1.Loaded event
If I do this it works, not sure this is what you want though:
<Window x:Class="DrawImage.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:DrawImage="clr-namespace:DrawImage"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DrawImage:UserControl1 x:Name="uc" Visibility="Hidden"/>
</Grid>
</Window>
void MainWindow_Closing(object sender, CancelEventArgs e)
{
uc.DataContext = "hello";
uc.Height = 100;
uc.Width = 100;
uc.Background = Brushes.LightBlue;
uc.Visibility = Visibility.Visible;
DrawToImage(uc);
}
EDIT
I am now able to reproduce the issue. If I set the DataContext in the Window contstructor then it works. If I set it in the Winndow_Closed event I get the exact same result that you get.
I guess there might no workaround since the WPF needs some time to actually render the text on the UI thread. If you render the PNG before the WPF has rendered the text on the UI thread it will not appear on the PNG.
A workaround does not seem to exist since the window will be destroyed when the Closed event handlers has been running. There is no way to block the UI thread on the one hand to prvevent to window from beeing detroyed when you on the other hand want the UI thread to render the control.
I'd suggest to render the image as soon as the control has been rendered and save the image file when the window is closed.
I posted an Article in my Blog (putting in the account the png Transparancy (causes the black background)):
Saving FrameworkElement as Image
FrameworkElement element = myControl.Content;
// you can set the size as you need.
Size theTargetSize = new Size(1500,2000)
element.Measure(new System.Windows.Size(double.PositiveInfinity, double.PositiveInfinity));
element.Arrange(new Rect(theTargetSize ));
// to affect the changes in the UI, you must call this method at the end to apply the new changes
element.UpdateLayout();
You can find the full cod in the Blog Post.

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...

Silverlight/WPF: Retreiving the size of a UIElement once it has been rendered on screen

I have the following simple piece of code:
var canvas = new Canvas();
foreach (var ztring in strings)
{
var textblock = new TextBlock();
textblock.Text = ztring;
panel.Children.Add(textblock);
textblock.Measure(infiniteSize);
}
At this point I would expect any of the size properties (Height/Width, ActualHeight/ActualWidth, DesiredSize, RenderSize) to give me the size of the textblock. None of them do.
ActualHeight always gives 16.0 no matter what size font. ActualWidth changes according to the text length but not the font size.
I change the font size on the parent container and not the TextBlock itself.
I feel like I am missing some basic element of understanding the manipulation of silverlight elements from within the codebehind.
The question is: how do I get the real actual pixel size of my TextBlock?
Below is a sample that adds a TextBlock to a Canvas using code behind and once the TextBlock is rendered, it displays its height in the title of the window. Is that what you are looking for?
XAML:
<Window x:Class="HeightTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel TextBlock.FontSize="30">
<Canvas Name="_canvas" Height="200"/>
</StackPanel>
</Window>
Code behind:
using System.Windows;
using System.Windows.Controls;
namespace HeightTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TextBlock textBlock = new TextBlock();
textBlock.Text = "Hello";
Canvas.SetLeft(textBlock, 25);
textBlock.Loaded +=
(sender, e) =>
{
Title = textBlock.ActualHeight.ToString();
};
_canvas.Children.Add(textBlock);
}
}
}
Have you tried using a real container like a Grid instead of Canvas?
What if you try reading the ActualSize property after the Measure using a Dispatcher.BeginInvoke?

WPF UserControl Design Time Size

When creating a UserControl in WPF, I find it convenient to give it some arbitrary Height and Width values so that I can view my changes in the Visual Studio designer. When I run the control, however, I want the Height and Width to be undefined, so that the control will expand to fill whatever container I place it in. How can I acheive this same functionality without having to remove the Height and Width values before building my control? (Or without using DockPanel in the parent container.)
The following code demonstrates the problem:
<Window x:Class="ExampleApplication3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:ExampleApplication3"
Title="Example" Height="600" Width="600">
<Grid Background="LightGray">
<loc:UserControl1 />
</Grid>
</Window>
The following definition of UserControl1 displays reasonably at design time but displays as a fixed size at run time:
<UserControl x:Class="ExampleApplication3.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid Background="LightCyan" />
</UserControl>
The following definition of UserControl1 displays as a dot at design time but expands to fill the parent Window1 at run time:
<UserControl x:Class="ExampleApplication3.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="LightCyan" />
</UserControl>
For Blend, a little known trick is to add these attributes to your usercontrol or window:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="600"
This will set the design height and width to 500 and 600 respectively. However this will only work for the blend designer. Not the Visual Studio Designer.
As far as the Visual Studio Designer your technique is all that works. Which is why I don't use the Visual Studio Designer. ;)
In Visual Studio add the Width and Height attribute to your UserControl XAML, but in the code-behind insert this
public UserControl1()
{
InitializeComponent();
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime)
{
this.Width = double.NaN; ;
this.Height = double.NaN; ;
}
}
This checks to see if the control is running in Design-mode. If not (i.e. runtime) it will set the Width and Height to NaN (Not a number) which is the value you set it to if you remove the Width and Height attributes in XAML.
So at design-time you will have the preset width and height (including if you put the user control in a form) and at runtime it will dock depending on its parent container.
Hope that helps.
Here are a list of Design-Time Attributes in the Silverlight Designer. They are the same for the WPF designer.
It lists all of the d: values available in the Designer such as d:DesignHeight, d:DesignWidth, d:IsDesignTimeCreatable, d:CreateList and several others.
I do this all the time. Simply set the width and height values to "auto" where you instantiate your control, and this will override the design-time values for that UserControl.
ie: <loc:UserControl1 Width="auto" Height="auto" />
Another option is to set a combination of MinWidth and MinHeight to a size that allows design-time work, while Width and Height remain "auto". Obviously, this only works if you don't need the UserControl to size smaller than the min values at runtime.
I was looking for similar solution like the one used in Blend and with your mentions I created simple behavior class with two attached properties Width & Height that are applied only in DesinTime
public static class DesignBehavior
{
private static readonly Type OwnerType = typeof (DesignBehavior);
#region Width
public static readonly DependencyProperty WidthProperty =
DependencyProperty.RegisterAttached(
"Width",
typeof (double),
OwnerType,
new FrameworkPropertyMetadata(double.NaN, new PropertyChangedCallback(WidthChangedCallback)));
public static double GetWidth(DependencyObject depObj)
{
return (double)depObj.GetValue(WidthProperty);
}
public static void SetWidth(DependencyObject depObj, double value)
{
depObj.SetValue(WidthProperty, value);
}
private static void WidthChangedCallback(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
if (DesignerProperties.GetIsInDesignMode(depObj)) {
depObj.SetValue(FrameworkElement.WidthProperty, e.NewValue);
}
}
#endregion
#region Height
public static readonly DependencyProperty HeightProperty =
DependencyProperty.RegisterAttached(
"Height",
typeof (double),
OwnerType,
new FrameworkPropertyMetadata(double.NaN, new PropertyChangedCallback(HeightChangedCallback)));
public static double GetHeight(DependencyObject depObj)
{
return (double)depObj.GetValue(HeightProperty);
}
public static void SetHeight(DependencyObject depObj, double value)
{
depObj.SetValue(HeightProperty, value);
}
private static void HeightChangedCallback(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
if (DesignerProperties.GetIsInDesignMode(depObj)) {
depObj.SetValue(FrameworkElement.HeightProperty, e.NewValue);
}
}
#endregion
}
Then in your UserControl you just set these properties in Xaml
<UserControl x:Class="ExtendedDataGrid.Views.PersonOverviewView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tool="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:b="clr-namespace:ExtendedDataGrid.Behaviors"
b:DesignBehavior.Width="600" b:DesignBehavior.Height="200">
<Grid>
...
</Grid>
</UserControl>
Use MinWidth and MinHeight on the control. That way, you'll see it in the designer, and at runtime it will size the way you want.
I do it similar, but my solution assures that if you add your control to an container in design mode, it will appear reasonably.
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
if (this.Parent != null)
{
this.Width = double.NaN;
this.Height = double.NaN;
}
}
what do you think?
Thanks to the original answerer for this solution! For those that are interested, here it is in VB:
If LicenseManager.UsageMode <> LicenseUsageMode.Designtime Then
Me.Width = Double.NaN
Me.Height = Double.NaN
End If
Some have suggested using the LicenseManager.UsageMode property which I've never seen before but I have used the following code.
if(!DesignerProperties.GetIsInDesignMode(this))
{
this.Width = double.NaN;
this.Height = double.NaN;
}
esskar,
I just want to add that you should generally always call the method of the base when overriding an "On" method.
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
base.OnVisualParentChanged(oldParent);
...
}
Great workaround by the way, I'm using it myself now too.

Resources