Borderless window Drop shadow Opacity elements issue - wpf

I am trying to create a borderless window in WPF. I am using the class mentioned in the link.
This works fine as long as all elements are all enabled (Opacity = 1). if i set opacity to 0.5, it is almost invisible.
The opacity works fine if i remove the code for the drop shadow. I am not sure what is the cause of this.
The class is the same one i am using as explained in link above. The rest of my code is below.
Any help is appreciated :
Girija
XAML:
<Window x:Class="Sample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Sample">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Margin="2" Text="Something" Grid.Row="0"/>
<Button Grid.Row="1" Content="Something" IsEnabled="False" Opacity="0.7"/>
</Grid>
Code behind :
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
/// <summary>
/// Raises the <see cref="E:System.Windows.FrameworkElement.Initialized"/> event.
/// This method is invoked whenever
/// <see cref="P:System.Windows.FrameworkElement.IsInitialized"/> is set to true internally.
/// </summary>
/// <param name="e">The <see cref="T:System.Windows.RoutedEventArgs"/>
/// that contains the event data.</param>
protected override void OnInitialized(EventArgs e)
{
//AllowsTransparency = false;
ResizeMode = ResizeMode.NoResize;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
WindowStyle = WindowStyle.None;
DwmDropShadow.DropShadowToWindow(this);
base.OnInitialized(e);
}
}
helper Class taken from above link :
class DwmDropShadow
{
[DllImport("dwmapi.dll", PreserveSig = true)]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
[DllImport("dwmapi.dll")]
private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref Margins pMarInset);
/// <summary>
/// Drops a standard shadow to a WPF Window, even if the window isborderless. Only works with DWM (Vista and Seven).
/// This method is much more efficient than setting AllowsTransparency to true and using the DropShadow effect,
/// as AllowsTransparency involves a huge permormance issue (hardware acceleration is turned off for all the window).
/// </summary>
/// <param name="window">Window to which the shadow will be applied</param>
public static void DropShadowToWindow(Window window)
{
if (!DropShadow(window))
{
window.SourceInitialized += new EventHandler(window_SourceInitialized);
}
}
private static void window_SourceInitialized(object sender, EventArgs e)
{
Window window = (Window)sender;
DropShadow(window);
window.SourceInitialized -= new EventHandler(window_SourceInitialized);
}
/// <summary>
/// The actual method that makes API calls to drop the shadow to the window
/// </summary>
/// <param name="window">Window to which the shadow will be applied</param>
/// <returns>True if the method succeeded, false if not</returns>
private static bool DropShadow(Window window)
{
try
{
WindowInteropHelper helper = new WindowInteropHelper(window);
int val = 2;
int ret1 = DwmSetWindowAttribute(helper.Handle, 2, ref val, 4);
if (ret1 == 0)
{
Margins m = new Margins { Bottom = 0, Left = 0, Right = 0, Top = 0 };
int ret2 = DwmExtendFrameIntoClientArea(helper.Handle, ref m);
return ret2 == 0;
}
else
{
return false;
}
}
catch (Exception ex)
{
// Probably dwmapi.dll not found (incompatible OS)
return false;
}
}
}

I dont know why are you using shadow effect by using dwmapi.dll. Why arent you using the hardware accelerated drop shadow effects from WPF Extended toolkit or from presentationframework.Aero (referred from .NET assemblies in visual studio)?
<ResourceDictionary Source="/presentationframework.Aero;V3.0.0.0;31bf3856ad364e35;component/themes/aero.normalcolor.xaml"/>
<DropShadowEffect ShadowDepth="2"></DropShadowEffect>

Here you go :)
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
AllowsTransparency="True"
Title="MainWindow"
Background="Transparent"
WindowStyle="None"
Height="350"
Width="500">
<Border Background="#ffFEFEFE" CornerRadius="5" BorderThickness="2" Margin="10" BorderBrush="#ffaaaaaa">
<Border.Effect>
<DropShadowEffect Direction="270"
ShadowDepth="5"
Opacity="0.8"
BlurRadius="7" />
</Border.Effect>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Margin="2"
Text="Something"
Grid.Row="0" />
<Button Grid.Row="1"
Content="Something"
IsEnabled="False"
Opacity="0.5" />
</Grid>
</Border>
</Window>

Use nonzero value for one of the margins, eg. 1, 0, 0, 0; or 1, 1, 1, 1; and use your own Margins:
[StructLayout(LayoutKind.Sequential)]
public struct Margins
{
public int Left;
public int Right;
public int Top;
public int Bottom;
}

Related

Removing or setting an absolute transform of XAML Element

I have a need to have a XAML object always be scaled 1:1, or atleast the imagebrush content, even though it's parent is in a viewbox and the content is compressed in the X direction.
An example: Viewbox contains Label & ImageBrush. I'd like the label text to scale, but only the ImageBrush size - when zoomed out it would only display the top corner of the content.
The viewmodel for the object does not have access to the scale factor. I've been looking for a way to remove or reset the viewbox transform, but I've been unable to find one. Is there one or will I have to propagate the current scale factor from the parent to the end viewmodel? I'd rather not mix presentation logic there unless I absolutely must.
Here's the current XAML I've got so far:
<ImageBrush ImageSource="{Binding Paint, Mode=OneWay, Converter={StaticResource BitmapToImageSourceConverter}}">
<ImageBrush.RelativeTransform>
<MatrixTransform>
<MatrixTransform.Matrix>
<MultiBinding Converter="{StaticResource TimelineMatrixConverter}">
<Binding />
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type MatrixTransform}}" Path="Matrix" />
</MultiBinding>
</MatrixTransform.Matrix>
</MatrixTransform>
</ImageBrush.RelativeTransform>
</ImageBrush>
Here is a basic exemple of what you can do. First extend the Viewbox class:
public class ViewboxEx : Viewbox
{
private FrameworkElement _child;
#region InvertScaleH
/// <summary>
/// InvertScaleH Read-Only Dependency Property
/// </summary>
private static readonly DependencyPropertyKey InvertScaleHPropertyKey
= DependencyProperty.RegisterReadOnly("InvertScaleH", typeof(double), typeof(ViewboxEx),
new FrameworkPropertyMetadata((double)1));
public static readonly DependencyProperty InvertScaleHProperty
= InvertScaleHPropertyKey.DependencyProperty;
/// <summary>
/// Gets the InvertScaleH property. This dependency property
/// indicates invert scale factor to compensate for horizontal scale fo the Viewbox.
/// </summary>
public double InvertScaleH
{
get { return (double)GetValue(InvertScaleHProperty); }
}
/// <summary>
/// Provides a secure method for setting the InvertScaleH property.
/// This dependency property indicates invert scale factor to compensate for horizontal scale fo the Viewbox.
/// </summary>
/// <param name="value">The new value for the property.</param>
protected void SetInvertScaleH(double value)
{
SetValue(InvertScaleHPropertyKey, value);
}
#endregion
#region InvertScaleV
/// <summary>
/// InvertScaleV Read-Only Dependency Property
/// </summary>
private static readonly DependencyPropertyKey InvertScaleVPropertyKey
= DependencyProperty.RegisterReadOnly("InvertScaleV", typeof(double), typeof(ViewboxEx),
new FrameworkPropertyMetadata((double)1));
public static readonly DependencyProperty InvertScaleVProperty
= InvertScaleVPropertyKey.DependencyProperty;
/// <summary>
/// Gets the InvertScaleV property. This dependency property
/// indicates invert scale factor to compensate for vertical scale fo the Viewbox.
/// </summary>
public double InvertScaleV
{
get { return (double)GetValue(InvertScaleVProperty); }
}
/// <summary>
/// Provides a secure method for setting the InvertScaleV property.
/// This dependency property indicates invert scale factor to compensate for vertical scale fo the Viewbox.
/// </summary>
/// <param name="value">The new value for the property.</param>
protected void SetInvertScaleV(double value)
{
SetValue(InvertScaleVPropertyKey, value);
}
#endregion
public ViewboxEx()
{
Loaded += OnLoaded;
SizeChanged += (_,__) => UpdateScale();
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
UpdateChild();
}
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
UpdateChild();
}
private void UpdateChild()
{
if (_child != null)
{
_child.SizeChanged -= OnChild_SizeChanged;
}
_child = Child as FrameworkElement;
if (_child != null)
{
_child.SizeChanged += OnChild_SizeChanged;
}
UpdateScale();
}
private void OnChild_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateScale();
}
private void UpdateScale()
{
if (_child == null)
return;
SetInvertScaleH(_child.ActualWidth / ActualWidth);
SetInvertScaleV(_child.ActualHeight / ActualHeight);
}
}
Basically what it does is to listen for the changes of the size of the Viewbox and its content and then calculate the invert scale factor. Now to use this scale factor:
<Window x:Class="YourNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNamespace"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:ViewboxEx>
<Grid Width="100" Height="100">
<Ellipse Fill="Green"/>
<TextBlock Text="123" Margin="10,10,0,0">
<TextBlock.RenderTransform>
<ScaleTransform
ScaleX="{Binding InvertScaleH, RelativeSource={RelativeSource AncestorType={x:Type local:ViewboxEx}}}"
ScaleY="{Binding InvertScaleV, RelativeSource={RelativeSource AncestorType={x:Type local:ViewboxEx}}}"/>
</TextBlock.RenderTransform>
</TextBlock>
</Grid>
</local:ViewboxEx>
</Grid>
</Window>
In this exemple the Ellipse follows the Viewbox size change as usual but the text keeps its size.
This is a basic implementation and may not work in all cases.

How can I print a collection of UserControls in WPF?

Say I have the following small UserControl:
<StackPanel>
<TextBlock Text="{Binding Title, StringFormat=Name: {0}}" FontWeight="Bold"/>
<Separator/>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=. ,StringFormat=Detail: {0}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Line Stroke="Black" HorizontalAlignment="Stretch" Margin="0,10"
X2="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
StrokeDashArray="2 2" StrokeThickness="1" />
</StackPanel>
My application will generate a collection of these controls and then they should be printed with automatic paginating. The number of items that is shown in the ItemsControl is variable. I scourged the Net, read the chapter about printing in Pro WPF 4.5 Unleashed, but still I do not see how to accomplish this.
Any guidance is very welcome.
EDIT: any guidance is welcome. As I have the data available in a view model it will not be too difficult to redirect its data elsewhere. But where?
Since you require automatic pagination, you'll need to create a DocumentPaginator object.
The following is stolen from this example and modified for your case:
/// <summary>
/// Document paginator.
/// </summary>
public class UserControlDocumentPaginator : DocumentPaginator
{
private readonly UserControlItem[] userControlItems;
private Size pageSize;
private int pageCount;
private int maxRowsPerPage;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="userControlItems">The list of userControl items to display.</param>
/// <param name="pageSize">The size of the page in pixels.</param>
public UserControlDocumentPaginator
(
UserControlItem[] userControlItems,
Size pageSize
)
{
this.userControlItems = userControlItems;
this.pageSize = pageSize;
PaginateUserControlItems();
}
/// <summary>
/// Computes the page count based on the number of userControl items
/// and the page size.
/// </summary>
private void PaginateUserControlItems()
{
double actualHeight;
foreach (var uc in userControlItems)
{
actualHeight += uc.ActualHeight;
}
pageCount = (int)Math.Ceiling(actualHeight / pageSize.Height);
}
/// <summary>
/// Gets a range of userControl items from an array.
/// </summary>
/// <param name="array">The userControl items array.</param>
/// <param name="start">Start index.</param>
/// <param name="end">End index.</param>
/// <returns></returns>
private static UserControlItem[] GetRange(UserControlItem[] array, int start, int end)
{
List<UserControlItem> userControlItems = new List<UserControlItem>();
for (int i = start; i < end; i++)
{
if (i >= array.Count())
{
break;
}
userControlItems.Add(array[i]);
}
return userControlItems.ToArray();
}
#region DocumentPaginator Members
/// <summary>
/// When overridden in a derived class, gets the DocumentPage for the
/// specified page number.
/// </summary>
/// <param name="pageNumber">
/// The zero-based page number of the document page that is needed.
/// </param>
/// <returns>
/// The DocumentPage for the specified pageNumber, or DocumentPage.Missing
/// if the page does not exist.
/// </returns>
public override DocumentPage GetPage(int pageNumber)
{
// Compute the range of userControl items to display
int start = pageNumber * maxRowsPerPage;
int end = start + maxRowsPerPage;
UserControlListPage page = new UserControlListPage(GetRange(userControlItems, start, end), pageSize);
page.Measure(pageSize);
page.Arrange(new Rect(pageSize));
return new DocumentPage(page);
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether
/// PageCount is the total number of pages.
/// </summary>
public override bool IsPageCountValid
{
get { return true; }
}
/// <summary>
/// When overridden in a derived class, gets a count of the number of
/// pages currently formatted.
/// </summary>
public override int PageCount
{
get { return pageCount; }
}
/// <summary>
/// When overridden in a derived class, gets or sets the suggested width
/// and height of each page.
/// </summary>
public override System.Windows.Size PageSize
{
get
{
return pageSize;
}
set
{
if (pageSize.Equals(value) != true)
{
pageSize = value;
PaginateUserControlItems();
}
}
}
/// <summary>
/// When overridden in a derived class, returns the element being paginated.
/// </summary>
public override IDocumentPaginatorSource Source
{
get { return null; }
}
#endregion
}
Then in your code behind for your Window, get a list of your user controls (replace YourUserControlContainer with the name of the container in your Window). Create a button named PrintButton and attach the Click event to the PrintButton_Click method below. Put in code behind where appropriate:
List<UserControl> userControlItems = new List<UserControl>();
// 8.5 x 11 paper
Size pageSize = new Size(816, 1056);
private void PrintButton_Click(object sender, RoutedEventArgs e)
{
userControlItems = YourUserControlContainer.Children.ToList();
UserControlDocumentPaginator paginator = new UserControlDocumentPaginator
(
userControlItems.ToArray(),
pageSize
);
var dialog = new PrintDialog();
if (dialog.ShowDialog() != true) return;
dialog.PrintDocument(paginator, "Custom Paginator Print Job");
}
Edit You're right, I forgot a class. You'll need something like this:
public partial class UserControlListPage : UserControl
{
private readonly UserControlItem[] userControlItems;
private readonly Size pageSize;
public UserControlListPage
(
UserControlItem[] userControlItems,
Size pageSize
)
{
InitializeComponent();
this.userControlItems = userControlItems;
this.pageSize = pageSize;
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
Point point = new Point(0, 0);
foreach (UserControlItem item in userControlItems)
{
point.X = 0;
itemImageSource = CopyControlToImageSource(item);
drawingContext.DrawImage(itemImageSource, point);
point.Y += itemImageSource.Height;
}
}
}
Then somewhere put this:
/// <summary>
/// Gets an image "screenshot" of the specified UIElement
/// </summary>
/// <param name="source">UIElement to screenshot</param>
/// <param name="scale" value="1">Scale to render the screenshot</param>
/// <returns>Byte array of BMP data</returns>
private static byte[] GetUIElementSnapshot(UIElement source, double scale = 1)
{
double actualHeight = source.RenderSize.Height;
double actualWidth = source.RenderSize.Width;
double renderHeight = actualHeight * scale;
double renderWidth = actualWidth * scale;
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)renderWidth, (int)renderHeight, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(source);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.PushTransform(new ScaleTransform(scale, scale));
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new System.Windows.Point(0, 0), new System.Windows.Point(actualWidth, actualHeight)));
}
renderTarget.Render(drawingVisual);
Byte[] _imageArray = null;
BmpBitmapEncoder bmpEncoder = new BmpBitmapEncoder();
bmpEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (MemoryStream outputStream = new MemoryStream())
{
bmpEncoder.Save(outputStream);
_imageArray = outputStream.ToArray();
}
return _imageArray;
}
public static System.Windows.Media.Imaging.BitmapImage CopyControlToImageSource(UIElement UserControl)
{
ImageConverter ic = new ImageConverter();
System.Drawing.Image img = (System.Drawing.Image)ic.ConvertFrom(GetUIElementSnapshot(UserControl));
MemoryStream ms = new MemoryStream();
img.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
System.Windows.Media.Imaging.BitmapImage image = new BitmapImage();
image.BeginInit();
ms.Seek(0, SeekOrigin.Begin);
image.StreamSource = ms;
image.EndInit();
return image;
}
Its a bit difficult to understand where you stuck at exactly since we do not have you code and we do not know about your data, however here is an simple example:
<Window x:Class="WpfApplication1.PrintVisualDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Print Visual Demo" Height="350" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.9*" />
<RowDefinition Height="0.2*"/>
</Grid.RowDefinitions>
<StackPanel Name="printPanel"
Grid.Row="0"
Margin="5">
<Label Content="Sweet Baby"
HorizontalAlignment="Center"
FontSize="40"
FontFamily="Calibri">
<Label.Foreground>
<LinearGradientBrush Opacity="1"
StartPoint="0,0.5" EndPoint="1,0.5">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="Red" Offset="0.5" />
<GradientStop Color="Green" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Label.Foreground>
</Label>
<Image Source="D:\Temp\baby.jpg"
Height="150"
Width="200">
</Image>
</StackPanel>
<Button Content="Print"
Grid.Row="1" Margin="5"
Height="40" Width="150"
HorizontalAlignment="Center"
VerticalAlignment="Center" Click="Button_Click" />
</Grid>
</Window>
private void Button_Click(object sender, RoutedEventArgs e)
{
PrintDialog pd = new PrintDialog();
if (pd.ShowDialog() != true) return;
pd.PrintVisual(printPanel, "Printing StackPanel...");
}
PrintVisual method should do the job.
If you want to scale your visual to fit the page you could follow those steps:
PrintDialog printDlg = new System.Windows.Controls.PrintDialog();
if (printDlg.ShowDialog() == true)
{
//get selected printer capabilities
System.Printing.PrintCapabilities capabilities = printDlg.PrintQueue.GetPrintCapabilities(printDlg.PrintTicket);
//get scale of the print wrt to screen of WPF visual
double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / this.ActualWidth, capabilities.PageImageableArea.ExtentHeight /
this.ActualHeight);
//Transform the Visual to scale
this.LayoutTransform = new ScaleTransform(scale, scale);
//get the size of the printer page
Size sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
//update the layout of the visual to the printer page size.
this.Measure(sz);
this.Arrange(new Rect(new Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight), sz));
//now print the visual to printer to fit on the one page.
printDlg.PrintVisual(this, "First Fit to Page WPF Print");
}
As you can see all you need to do is remeasure and rearrange the specific element with new size and then you can call PrintVisual method.
I hope this helps you any futher. Consider post is more code or upload your project online somewhere.

How to create a semi transparent form in WPF

I have to develop a semi-transparent form in WPF, but controls should not be transparent.
I have tried different things like setting opacity=0.5 but no result.
I know that AllowTransparency can be set to True only if WindowStyle set to None, but I need to show Border as well
UPDATE:
Pavlo Glazkov, What is your opinion for this solution
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" Opacity="1" AllowsTransparency="True" WindowStyle="None" Background="Transparent">
<Grid Background="Transparent">
<Border Margin="2,2,12,34" Name="border1" BorderBrush="Lavender" BorderThickness="5" CornerRadius="20,0,20,0"></Border>
<Button Height="23" Margin="93,101,110,0" Name="button1" VerticalAlignment="Top" Background="CadetBlue" Foreground="White">Hello WPF</Button>
<Button Height="24" Margin="0,8,20,0" Name="button2" VerticalAlignment="Top" HorizontalAlignment="Right" Width="21" Click="button2_Click">X</Button>
</Grid>
</Window>
First, you need to set AllowTransperency to True. Then, you can set the background of the window to a transparent (to desired extent) brush:
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStyle="None"
AllowsTransparency="True"
Background="{DynamicResource WindowBackground}">
<Window.Resources>
<SolidColorBrush x:Key="WindowBackground"
Color="White"
Opacity="0.5"/>
</Window.Resources>
...
</Window>
Please note that AllowTransperency can be set to True only if WindowStyle set to None.
Update:
If you don't want to set WindowStyle to None and would like to keep standart border and window buttons there is an alternative that will work only on Windows Vista/7 with Windows Aero theme.
The trick is that you can extend the "Glass" area to the whole window using the following code:
public static class WindowUtils
{
/// <summary>
/// Extends the glass area into the client area of the window
/// </summary>
/// <param name="window">Window to extend the glass on.</param>
/// <param name="thikness">Thickness of border to extend.</param>
public static void ExtendGlass(this Window window, Thickness thikness) {
try {
int isGlassEnabled = 0;
Win32.DwmIsCompositionEnabled(ref isGlassEnabled);
if (Environment.OSVersion.Version.Major > 5 && isGlassEnabled > 0) {
// Get the window handle
var helper = new WindowInteropHelper(window);
var mainWindowSrc = HwndSource.FromHwnd(helper.Handle);
if (mainWindowSrc != null) {
if (mainWindowSrc.CompositionTarget != null) {
mainWindowSrc.CompositionTarget.BackgroundColor = Colors.Transparent;
}
// Get the dpi of the screen
System.Drawing.Graphics desktop =
System.Drawing.Graphics.FromHwnd(mainWindowSrc.Handle);
float dpiX = desktop.DpiX / 96;
float dpiY = desktop.DpiY / 96;
// Set Margins
var margins = new MARGINS {
cxLeftWidth = (int)(thikness.Left * dpiX),
cxRightWidth = (int)(thikness.Right * dpiX),
cyBottomHeight = (int)(thikness.Bottom * dpiY),
cyTopHeight = (int)(thikness.Top * dpiY)
};
window.Background = Brushes.Transparent;
Win32.DwmExtendFrameIntoClientArea(mainWindowSrc.Handle, ref margins);
}
}
else {
window.Background = SystemColors.WindowBrush;
}
}
catch (DllNotFoundException) {
}
}
}
public class Win32
{
[DllImport("dwmapi.dll")]
public static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset);
[DllImport("dwmapi.dll")]
public static extern int DwmIsCompositionEnabled(ref int en);
[DllImport("user32.dll")]
public static extern bool SetCursorPos(int X, int Y);
[DllImport("User32", EntryPoint = "ClientToScreen", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern int ClientToScreen(IntPtr hWnd, [In, Out] POINT pt);
}
[StructLayout(LayoutKind.Sequential)]
public struct MARGINS
{
public int cxLeftWidth;
public int cxRightWidth;
public int cyTopHeight;
public int cyBottomHeight;
}
[StructLayout(LayoutKind.Sequential)]
public class POINT
{
public int x = 0;
public int y = 0;
}
To extend the glass to the whole window you need to call the ExtendGlass extension method in a SizeChanged event handler of the window and pass a Thickness that covers whole window:
public MyWindow() {
InitializeComponent();
SizeChanged += OnSizeChanged;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e) {
double horisontalThickness = Width / 2;
double verticalThickness = Height / 2;
var glassThickness = new Thickness(horisontalThickness, verticalThickness, horisontalThickness, verticalThickness);
this.ExtendGlass(glassThickness);
}
You could try this, it will create a Glass Background for your Window (looks like the Vista and Windows7 transparency effect)
Here is some further explanation from Microsoft.

Animating Grid Column or Grid Row in XAML?

Is there any way I can animate Grid column width or Grid row height from XAML?
How about a work around? Why not place a grid(or any other desired control) inside the particular row that you want to animate, set the row height to "Auto", then animate the height of the control. It worked for me.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button x:Name="ExpandCollapseBtn" Width="100" Click="ExpandCollapse_Click"/>
<WrapPanel x:Name="ToolBox" Grid.Row="1" Height="0">
<Button Content="1" Width="50" Height="50"/>
<Button Content="2" Width="50" Height="50"/>
<Button Content="3" Width="50" Height="50"/>
<Button Content="4" Width="50" Height="50"/>
</WrapPanel>
</Grid>
Code behind:
private bool Expanded = false;
void ExpandCollapse_Click(object sender, RoutedEventArgs e)
{
if (Expanded)
{
var anim = new DoubleAnimation(0, (Duration)TimeSpan.FromSeconds(0.3));
anim.Completed += (s, _) => Expanded = false;
ToolBox.BeginAnimation(ContentControl.HeightProperty, anim);
}
else
{
var anim = new DoubleAnimation(100, (Duration)TimeSpan.FromSeconds(0.3));
anim.Completed += (s, _) => Expanded = true;
ToolBox.BeginAnimation(ContentControl.HeightProperty, anim);
}
}
I admit its not what you are looking for. But its a quick solution(Assuming of course that ultimately you want the UIElement placed inside the grid animated by animating the grid row). You can similarly do it for column width.
The ColumnDefinition.Width and RowDefinition.Height properties are of type GridLength, and there is no built-in animations for this type. So if you want to do that, you will probably have to create your own GridLengthAnimation class. That's probably not too impossible if you take DoubleAnimation as an example, but not easy either...
EDIT: actually, there are several interesting results if you search "GridLength animation" on Google...
http://windowsclient.net/learn/video.aspx?v=70654
http://marlongrech.wordpress.com/2007/08/20/gridlength-animation/
http://www.codeproject.com/KB/WPF/GridLengthAnimation.aspx
I got tired of having to fiddle with XAML to animate grid rows and columns a while ago so I wrote a couple of methods to do it totally from code.
With these you can expand/shrink columns and rows from code with one line:
Animation.AnimationHelper.AnimateGridColumnExpandCollapse(LeftColumn, true, expandedHeight, currentWidth, LeftColumn.MinWidth, 0, 200);
One important thing to note is setting the animation to null on completion. If you don't do this, the grid is still under control of the animation when the animation is complete. This might be fine if the grid doesn't have a splitter, but if the grid has a splitter and you want to be able to resize it manually after the animation completes, then you have to set the animation to null after it completes.
Here are the methods:
/// <summary>
/// Animate expand/collapse of a grid column.
/// </summary>
/// <param name="gridColumn">The grid column to expand/collapse.</param>
/// <param name="expandedWidth">The expanded width.</param>
/// <param name="milliseconds">The milliseconds component of the duration.</param>
/// <param name="collapsedWidth">The width when collapsed.</param>
/// <param name="minWidth">The minimum width of the column.</param>
/// <param name="seconds">The seconds component of the duration.</param>
/// <param name="expand">If true, expand, otherwise collapse.</param>
public static void AnimateGridColumnExpandCollapse(ColumnDefinition gridColumn, bool expand, double expandedWidth, double collapsedWidth,
double minWidth, int seconds, int milliseconds)
{
if( expand && gridColumn.ActualWidth >= expandedWidth)
// It's as wide as it needs to be.
return;
if (!expand && gridColumn.ActualWidth == collapsedWidth)
// It's already collapsed.
return;
Storyboard storyBoard = new Storyboard();
GridLengthAnimation animation = new GridLengthAnimation();
animation.From = new GridLength(gridColumn.ActualWidth);
animation.To = new GridLength(expand ? expandedWidth : collapsedWidth);
animation.Duration = new TimeSpan(0, 0, 0, seconds, milliseconds);
// Set delegate that will fire on completion.
animation.Completed += delegate
{
// Set the animation to null on completion. This allows the grid to be resized manually
gridColumn.BeginAnimation(ColumnDefinition.WidthProperty, null);
// Set the final value manually.
gridColumn.Width = new GridLength(expand ? expandedWidth : collapsedWidth);
// Set the minimum width.
gridColumn.MinWidth = minWidth;
};
storyBoard.Children.Add(animation);
Storyboard.SetTarget(animation, gridColumn);
Storyboard.SetTargetProperty(animation, new PropertyPath(ColumnDefinition.WidthProperty));
storyBoard.Children.Add(animation);
// Begin the animation.
storyBoard.Begin();
}
/// <summary>
/// Animate expand/collapse of a grid row.
/// </summary>
/// <param name="gridRow">The grid row to expand/collapse.</param>
/// <param name="expandedHeight">The expanded height.</param>
/// <param name="collapsedHeight">The collapesed height.</param>
/// <param name="minHeight">The minimum height.</param>
/// <param name="milliseconds">The milliseconds component of the duration.</param>
/// <param name="seconds">The seconds component of the duration.</param>
/// <param name="expand">If true, expand, otherwise collapse.</param>
public static void AnimateGridRowExpandCollapse(RowDefinition gridRow, bool expand, double expandedHeight, double collapsedHeight, double minHeight, int seconds, int milliseconds)
{
if (expand && gridRow.ActualHeight >= expandedHeight)
// It's as high as it needs to be.
return;
if (!expand && gridRow.ActualHeight == collapsedHeight)
// It's already collapsed.
return;
Storyboard storyBoard = new Storyboard();
GridLengthAnimation animation = new GridLengthAnimation();
animation.From = new GridLength(gridRow.ActualHeight);
animation.To = new GridLength(expand ? expandedHeight : collapsedHeight);
animation.Duration = new TimeSpan(0, 0, 0, seconds, milliseconds);
// Set delegate that will fire on completioon.
animation.Completed += delegate
{
// Set the animation to null on completion. This allows the grid to be resized manually
gridRow.BeginAnimation(RowDefinition.HeightProperty, null);
// Set the final height.
gridRow.Height = new GridLength(expand ? expandedHeight : collapsedHeight);
// Set the minimum height.
gridRow.MinHeight = minHeight;
};
storyBoard.Children.Add(animation);
Storyboard.SetTarget(animation, gridRow);
Storyboard.SetTargetProperty(animation, new PropertyPath(RowDefinition.HeightProperty));
storyBoard.Children.Add(animation);
// Begin the animation.
storyBoard.Begin();
}
I built upon the AnimationHelper class provided by Nigel Shaw and wrapped it in a reusable GridAnimationBehavior which can be attached to the RowDefinition and ColumnDefinition elements.
/// <summary>
/// Wraps the functionality provided by the <see cref="AnimationHelper"/> class
/// in a behavior which can be used with the <see cref="ColumnDefinition"/>
/// and <see cref="RowDefinition"/> types.
/// </summary>
public class GridAnimationBehavior : DependencyObject
{
#region Attached IsExpanded DependencyProperty
/// <summary>
/// Register the "IsExpanded" attached property and the "OnIsExpanded" callback
/// </summary>
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.RegisterAttached("IsExpanded", typeof(bool), typeof(GridAnimationBehavior),
new FrameworkPropertyMetadata(OnIsExpandedChanged));
public static void SetIsExpanded(DependencyObject dependencyObject, bool value)
{
dependencyObject.SetValue(IsExpandedProperty, value);
}
#endregion
#region Attached Duration DependencyProperty
/// <summary>
/// Register the "Duration" attached property
/// </summary>
public static readonly DependencyProperty DurationProperty =
DependencyProperty.RegisterAttached("Duration", typeof(TimeSpan), typeof(GridAnimationBehavior),
new FrameworkPropertyMetadata(TimeSpan.FromMilliseconds(200)));
public static void SetDuration(DependencyObject dependencyObject, TimeSpan value)
{
dependencyObject.SetValue(DurationProperty, value);
}
private static TimeSpan GetDuration(DependencyObject dependencyObject)
{
return (TimeSpan)dependencyObject.GetValue(DurationProperty);
}
#endregion
#region GridCellSize DependencyProperty
/// <summary>
/// Use a private "GridCellSize" dependency property as a temporary backing
/// store for the last expanded grid cell size (row height or column width).
/// </summary>
private static readonly DependencyProperty GridCellSizeProperty =
DependencyProperty.Register("GridCellSize", typeof(double), typeof(GridAnimationBehavior),
new UIPropertyMetadata(0.0));
private static void SetGridCellSize(DependencyObject dependencyObject, double value)
{
dependencyObject.SetValue(GridCellSizeProperty, value);
}
private static double GetGridCellSize(DependencyObject dependencyObject)
{
return (double)dependencyObject.GetValue(GridCellSizeProperty);
}
#endregion
/// <summary>
/// Called when the attached <c>IsExpanded</c> property changed.
/// </summary>
private static void OnIsExpandedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var duration = GetDuration(dependencyObject);
var rowDefinition = dependencyObject as RowDefinition;
if (rowDefinition != null)
{
// The IsExpanded attached property of a RowDefinition changed
if ((bool)e.NewValue)
{
var expandedHeight = GetGridCellSize(rowDefinition);
if (expandedHeight > 0)
{
// Animate row height back to saved expanded height.
AnimationHelper.AnimateGridRowExpandCollapse(rowDefinition, true, expandedHeight, rowDefinition.ActualHeight, 0, duration);
}
}
else
{
// Save expanded height and animate row height down to zero.
SetGridCellSize(rowDefinition, rowDefinition.ActualHeight);
AnimationHelper.AnimateGridRowExpandCollapse(rowDefinition, false, rowDefinition.ActualHeight, 0, 0, duration);
}
}
var columnDefinition = dependencyObject as ColumnDefinition;
if (columnDefinition != null)
{
// The IsExpanded attached property of a ColumnDefinition changed
if ((bool)e.NewValue)
{
var expandedWidth = GetGridCellSize(columnDefinition);
if (expandedWidth > 0)
{
// Animate column width back to saved expanded width.
AnimationHelper.AnimateGridColumnExpandCollapse(columnDefinition, true, expandedWidth, columnDefinition.ActualWidth, 0, duration);
}
}
else
{
// Save expanded width and animate column width down to zero.
SetGridCellSize(columnDefinition, columnDefinition.ActualWidth);
AnimationHelper.AnimateGridColumnExpandCollapse(columnDefinition, false, columnDefinition.ActualWidth, 0, 0, duration);
}
}
}
}
Note that I tweaked Nigel's code a bit to use a parameter of type TimeSpan for the animation duration instead of separate seconds and milliseconds parameters.
This behavior makes the animation of grid rows/columns MVVM friendly (XAML-only, no code behind required). Example:
<Grid.RowDefinitions>
<RowDefinition Height="*" Behaviors:GridAnimationBehavior.IsExpanded="{Binding IsUpperPaneVisible}" />
<RowDefinition Height="*" />
<RowDefinition Height="*" Behaviors:GridAnimationBehavior.IsExpanded="{Binding IsLowerPaneVisible}" />
</Grid.RowDefinitions>
I added this answer because the original poster asked for a pure XAML solution.
The MahApps.Metro library has a built-in control for this. The source can be found here.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="48" x:Name="HamburgerMenuColumn" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.Resources>
<Storyboard x:Key="CloseMenu" Storyboard.TargetName="HamburgerMenuColumn" Storyboard.TargetProperty="(ColumnDefinition.Width)">
<metro:GridLengthAnimation To="48" Duration="00:00:00"></metro:GridLengthAnimation>
</Storyboard>
</Grid.Resources>
</Grid>
This work for me
In XAML:
<Grid >
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition x:Name="SecondRow" Height="100"/>
</Grid.RowDefinitions>
</Grid >
Code Behind:
if (SecondRow.Height == new GridLength(0))
{
Task.Run(() => {
for(int i = 0; i <= 20; i++)
{
this.Dispatcher.Invoke((Action)delegate { SecondRow.Height = new GridLength(5*i); });
Task.Delay(1).Wait();
}
});
}
else
{
Task.Run(() => {
for (int i = 20; i >= 0; i--)
{
this.Dispatcher.Invoke((Action)delegate { SecondRow.Height = new GridLength(5 * i); });
Task.Delay(1).Wait();
}
});
}

WPF: Why is DataContextChanged not raised on a logical child?

I have a problem with DataContextChanged not being raised on a logical child of my custom Panel control. I've narrowed it down to this:
Starting from a wizard-generated WPF application I add:
private void Window_Loaded( object sender, RoutedEventArgs e )
{
var elt = new FrameworkElement();
this.AddLogicalChild( elt );
DataContext = 42;
Debug.Assert( (int)elt.DataContext == 42 );
}
As I understand, this works because the DataContext is an inheritable dependency property.
Now, I add event handlers for DataContextChanged both on the Window (this) and its logical child:
this.DataContextChanged +=
delegate { Debug.WriteLine( "this:DataContextChanged" ); };
elt.DataContextChanged +=
delegate { Debug.WriteLine( "elt:DataContextChanged" ); };
If I run this, only the first event handler will execute. Why is this? If instead of AddLogicalChild( elt ) I do the following:
this.Content = elt;
both handlers will execute. But this is not an option in my case - I'm adding FrameworkContentElements to my control which aren't supposed to be visual children.
What's going on here? Should I do something more besides AddLogicalChild() to make it work?
(Fortunately, there is a rather simple workaround - just bind the DataContext of the element to the DataContext of the window)
BindingOperations.SetBinding( elt, FrameworkElement.DataContextProperty,
new Binding( "DataContext" ) { Source = this } );
Thank you.
You need to override the LogicalChildren property too:
protected override System.Collections.IEnumerator LogicalChildren
{
get { yield return elt; }
}
Of course, you'll want to return any logical children defined by the base implementation, too.
I'd like to add some advice to Kent's answer if someone runs into a similar problems:
If you create a custom control with multiple content objects you should ensure:
Content objects are added to the LogicalTree via AddLogicalChild()
Create your own Enumerator class and return an instance of that in the overriden LogicalChildren property
If you don't add the content objects to the logical tree you might run into problems like Bindings with ElementNames can not be resolved (ElementName is resolved by FindName which in turn uses the LogicalTree to find the elements).
What makes it even more dangerous is that my experience is that if you miss to add the objects to the logical tree the ElementName resolving works in some scenarios and it doesn't work in other scenarios.
If you don't override LogicalChildren the DataContext is not updated like described above.
Here a short example with a simple SplitContainer:
SplitContainer:
public class SplitContainer : Control
{
static SplitContainer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SplitContainer), new FrameworkPropertyMetadata(typeof(SplitContainer)));
}
/// <summary>
/// Identifies the <see cref="Child1"/> property.
/// </summary>
public static readonly DependencyProperty Child1Property =
DependencyProperty.Register(nameof(Child1), typeof(object), typeof(SplitContainer), new FrameworkPropertyMetadata(Child1PropertyChangedCallback));
/// <summary>
/// Left Container
/// </summary>
public object Child1
{
get { return (object)GetValue(Child1Property); }
set { SetValue(Child1Property, value); }
}
/// <summary>
/// Identifies the <see cref="Child2"/> property.
/// </summary>
public static readonly DependencyProperty Child2Property =
DependencyProperty.Register(nameof(Child2), typeof(object), typeof(SplitContainer), new FrameworkPropertyMetadata(Child2PropertyChangedCallback));
/// <summary>
/// Right Container
/// </summary>
public object Child2
{
get { return (object)GetValue(Child2Property); }
set { SetValue(Child2Property, value); }
}
private static void Child1PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var splitContainer = (SplitContainer)d;
if (e.OldValue != null)
{
splitContainer.RemoveLogicalChild(e.OldValue);
}
if (e.NewValue != null)
{
splitContainer.AddLogicalChild(((object)e.NewValue));
}
}
private static void Child2PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var splitContainer = (SplitContainer)d;
if (e.OldValue != null)
{
splitContainer.RemoveLogicalChild(e.OldValue);
}
if (e.NewValue != null)
{
splitContainer.AddLogicalChild(((object)e.NewValue));
}
}
protected override IEnumerator LogicalChildren
{
get
{
return new SplitContainerLogicalChildrenEnumerator(this);
}
}
}
SplitContainerLogicalChildrenEnumerator:
internal class SplitContainerLogicalChildrenEnumerator : IEnumerator
{
private readonly SplitContainer splitContainer;
private int index = -1;
public SplitContainerLogicalChildrenEnumerator(SplitContainer splitContainer)
{
this.splitContainer = splitContainer;
}
public object Current
{
get
{
if (index == 0)
{
return splitContainer.Child1;
}
else if (index == 1)
{
return splitContainer.Child2;
}
throw new InvalidOperationException("No child for this index available");
}
}
public bool MoveNext()
{
index++;
return index < 2;
}
public void Reset()
{
index = -1;
}
}
Style (e.g. in Themes/generic.xaml):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=PresentationFramework"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:SplitContainerElementNameProblem">
<Style TargetType="{x:Type local:SplitContainer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SplitContainer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
Content="{TemplateBinding Child1}" />
<ContentPresenter Grid.Column="1"
Content="{TemplateBinding Child2}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Sample which demonstrates that each Binding works fine:
XAML:
<Window x:Class="SplitContainerElementNameProblem.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:SplitContainerElementNameProblem"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBox x:Name="text1" Text="abc" />
</Grid>
<local:SplitContainer Grid.Row="1">
<local:SplitContainer.Child1>
<TextBox x:Name="text2"
Text="{Binding ElementName=text1, Path=Text}" />
</local:SplitContainer.Child1>
<local:SplitContainer.Child2>
<StackPanel>
<TextBox x:Name="text3"
Text="{Binding ElementName=text2, Path=Text}" />
<TextBox x:Name="text4"
Text="{Binding MyName}" />
</StackPanel>
</local:SplitContainer.Child2>
</local:SplitContainer>
</Grid>
XAML.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
MyName = "Bruno";
}
public string MyName
{
get;
set;
}
}

Resources