In WPF there is an Effect property of most controls such as Grid.Effect which allows creating a blurred grid. I wanted to see how can I create a popup with a blurred background in WinRT?
An alternative to not doing it or using DirectX might be to use the new RenderTargetBitmap class with the RenderAsync() method that allows you to get access to the pixels of the control from C# or VB level (assuming that is what you are using), which might be easier than DirectX and actually fast enough for some basic effects to run on the CPU. I've done it in the FxContentControl in the WinRT XAML Toolkit last weekend and it seems to work OK in some simple scenarios with the main problem being - when to update the effect.
The control currently doesn't blur anything, but rather finds the expanded pixels of the content and makes them black, serving as a poor man's way to generate strokes for a TextBlock or for any other control with some transparent and some non-transparent areas. You could change it to blur or add an option to do it on demand fairly easily.
Here's the default style of the templated control:
<Style
TargetType="controls:FxContentControl">
<Setter
Property="HorizontalContentAlignment"
Value="Left" />
<Setter
Property="VerticalContentAlignment"
Value="Top" />
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="controls:FxContentControl">
<Grid>
<Image
x:Name="BackgroundFxImage"
Stretch="None"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
<Grid
x:Name="RenderedGrid"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<ContentPresenter
x:Name="ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
Content="{TemplateBinding Content}" />
</Grid>
<Image
x:Name="ForegroundFxImage"
Stretch="None"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's the code:
using System;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using WinRTXamlToolkit.Imaging;
namespace WinRTXamlToolkit.Controls
{
public class FxContentControl : ContentControl
{
private Image _backgroundFxImage;
private Image _foregroundFxImage;
private ContentPresenter _contentPresenter;
private Grid _renderedGrid;
public FxContentControl()
{
this.DefaultStyleKey = typeof(FxContentControl);
}
protected override async void OnApplyTemplate()
{
base.OnApplyTemplate();
_backgroundFxImage = this.GetTemplateChild("BackgroundFxImage") as Image;
_foregroundFxImage = this.GetTemplateChild("ForegroundFxImage") as Image;
_contentPresenter = this.GetTemplateChild("ContentPresenter") as ContentPresenter;
_renderedGrid = this.GetTemplateChild("RenderedGrid") as Grid;
if (_renderedGrid != null)
{
_renderedGrid.SizeChanged += this.OnContentPresenterSizeChanged;
}
if (_renderedGrid.ActualHeight > 0)
{
await this.UpdateFx();
}
}
private async void OnContentPresenterSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
{
await this.UpdateFx();
}
private async Task UpdateFx()
{
await this.UpdateBackgroundFx();
}
private async Task UpdateBackgroundFx()
{
if (_renderedGrid.ActualHeight < 1 ||
_backgroundFxImage == null)
{
return;
}
var rtb = new RenderTargetBitmap();
await rtb.RenderAsync(_renderedGrid);
var pw = rtb.PixelWidth;
var ph = rtb.PixelHeight;
var wb = _backgroundFxImage.Source as WriteableBitmap;
if (wb == null ||
wb.PixelWidth != pw ||
wb.PixelHeight != ph)
{
wb = new WriteableBitmap(pw, ph);
}
await ProcessContentImage(rtb, wb, pw, ph);
_backgroundFxImage.Source = wb;
}
protected virtual async Task ProcessContentImage(RenderTargetBitmap rtb, WriteableBitmap wb, int pw, int ph)
{
var rtbBuffer = await rtb.GetPixelsAsync();
var rtbPixels = rtbBuffer.GetPixels();
var wbBuffer = wb.PixelBuffer;
var wbPixels = wbBuffer.GetPixels();
// Expand
int expansion = 1;
for (int x = 0; x < pw; x++)
for (int y = 0; y < ph; y++)
{
int x1min = Math.Max(0, x - expansion);
int x1max = Math.Min(x + expansion, pw - 1);
int y1min = Math.Max(0, y - expansion);
int y1max = Math.Min(y + expansion, ph - 1);
//bool found = false;
byte maxa = 0;
for (int x1 = x1min; x1 <= x1max; x1++)
for (int y1 = y1min; y1 <= y1max; y1++)
{
var a = rtbPixels.Bytes[4 * (y1 * pw + x1) + 3];
if (a > maxa)
maxa = a;
}
wbPixels.Bytes[4 * (y * pw + x)] = 0;
wbPixels.Bytes[4 * (y * pw + x) + 1] = 0;
wbPixels.Bytes[4 * (y * pw + x) + 2] = 0;
wbPixels.Bytes[4 * (y * pw + x) + 3] = maxa;
}
wbPixels.UpdateFromBytes();
}
}
}
It also uses the following IBuffer wrapper:
using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Storage.Streams;
namespace WinRTXamlToolkit.Imaging
{
// ReSharper disable InconsistentNaming - This class extends IBuffer
/// <summary>
/// Contains extensions for an IBuffer interface in the context of a WriteableBitmap,
/// which exposes one to access its pixels.
/// </summary>
public static class IBufferExtensions
// ReSharper restore InconsistentNaming
{
/// <summary>
/// Gives access to the pixels of a WriteableBitmap given an IBuffer
/// exposed by Pixels property.
/// </summary>
/// <remarks>
/// Note that creating this object copies the pixels buffer
/// into the Bytes byte array for quick pixel access
/// and the array needs to be copied back to the pixels buffer
/// to update the bitmap with a call to UpdateFromBytes().
/// This is acceptable for convenience and possibly best for
/// performance in some scenarios, but it does add some upfront
/// overhead as well overhead to update the bitmap at the end.
/// This is only a theory and for better performance it might be
/// good to test different approaches.
/// The goal of this approach is code simplicity. For best performance
/// using native code and/or DirectX is recommended.
/// </remarks>
public class PixelBufferInfo
{
private readonly Stream _pixelStream;
/// <summary>
/// The bytes of the pixel stream.
/// </summary>
public byte[] Bytes;
/// <summary>
/// Initializes a new instance of the <see cref="PixelBufferInfo" /> class.
/// </summary>
/// <param name="pixelBuffer">The pixel buffer returned by WriteableBitmap.PixelBuffer.</param>
public PixelBufferInfo(IBuffer pixelBuffer)
{
_pixelStream = pixelBuffer.AsStream();
this.Bytes = new byte[_pixelStream.Length];
_pixelStream.Seek(0, SeekOrigin.Begin);
_pixelStream.Read(this.Bytes, 0, Bytes.Length);
//this.Pixels = bytes.ToPixels();
}
/// <summary>
/// Updates the associated pixel buffer from bytes.
/// </summary>
public void UpdateFromBytes()
{
_pixelStream.Seek(0, SeekOrigin.Begin);
_pixelStream.Write(Bytes, 0, Bytes.Length);
}
}
/// <summary>
/// Gets the pixels access wrapper for a PixelBuffer property of a WriteableBitmap.
/// </summary>
/// <param name="pixelBuffer">The pixel buffer.</param>
/// <returns></returns>
public static PixelBufferInfo GetPixels(this IBuffer pixelBuffer)
{
return new PixelBufferInfo(pixelBuffer);
}
}
}
Effects are not supported in WinRT in XAML. You can use DirectX interop and put shaders there using C++ (SwapChainPanel).
As you've noticed, Effects are not available currently in WinRT.
Ideas:
I'd suggest considering not creating a blurred background effect as it would not be a consistent experience across Windows Store applications. Effects, at least in the Widows Application Store "design language" just aren't a good match.
Alternatively, you could just overlay a semi-transparent image with some noise to it to give a feeling at least of a non-uniform effect (rather than just a solid color made partially transparent).
Or, maybe just "darken" the background (again, an overlay would make this easy enough) to give a more "lightbox" style effect.
While you may be able to use interop, that likely increases the complexity of your application dramatically, especially when it's for a simple feature such as this.
Related
I'm finding it awfully hard to see how to simply cover a rectangular XAML element with repeating copies of a bitmap! I am using WinUI 3 with Windows App SDK. I would like to use the repeating image as a background element in my app.
It would seem to involve the composition API. Some tantalizing clues are given by Deiderik Krohls and by JetChopper ... however (a) there does not seem to be a stable released NuGet package for the required interface and (b) this seems like a very complicated way to do something that should be simple and (c) these solutions would seem to require extra work to integrate with WinUI 3 classes such as ImageSource and BitmapImage.
Any suggestions?
You can use a Direct2D effect, the Tile Effect for that. This effect is hardware accelerated. Microsoft provides a nuget called Win2D that enables that for WinUI: Microsoft.Graphics.Win2D
Once you have created a standard WinUI3 application project, add this nuget, and for this XAML:
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<canvas:CanvasControl
x:Name="myCanvas"
Width="128"
Height="128"
CreateResources="myCanvas_CreateResources"
Draw="myCanvas_Draw" />
</StackPanel>
You can display a repetition of an image with a C# code like this:
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
// handle canvas' CreateResources event for Win2D (Direct2D) resources
private void myCanvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args)
=> args.TrackAsyncAction(CreateResources(sender).AsAsyncAction());
// create all needed resources async (here a bitmap)
CanvasBitmap _canvasBitmap;
private async Task CreateResources(CanvasControl sender)
{
// this is my 32x32 image downloaded from https://i.stack.imgur.com/454HU.jpg?s=32&g=1
_canvasBitmap = await CanvasBitmap.LoadAsync(sender, #"c:\downloads\smo.jpg");
}
// handle canvas' Draw event
// check quickstart https://microsoft.github.io/Win2D/WinUI3/html/QuickStart.htm
private void myCanvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
// create an intermediate command list as a feed to the Direct2D effect
using var list = new CanvasCommandList(sender);
using var session = list.CreateDrawingSession();
session.DrawImage(_canvasBitmap);
// create the Direct2D effect (here Tile effect https://learn.microsoft.com/en-us/windows/win32/direct2d/tile)
using var tile = new TileEffect();
tile.Source = list;
// use image size as source rectangle
tile.SourceRectangle = _canvasBitmap.Bounds;
// draw the effect (using bitmap as input)
args.DrawingSession.DrawImage(tile);
}
}
Here is the result with my StackOverflow avatar as the bitmap source:
The image is 32x32 and the canvas is 128x128 so we have 4x4 tiles.
You can use the TilesBrush from the CommunityToolkit.
Install the CommunityToolkit.WinUI.UI.Media NuGet package and try this code:
<Window
x:Class="TileBrushes.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:toolkit="using:CommunityToolkit.WinUI.UI.Media"
mc:Ignorable="d">
<Grid ColumnDefinitions="*,*">
<Border Grid.Column="0">
<TextBlock Text="No tiles" />
</Border>
<Border Grid.Column="1">
<Border.Background>
<toolkit:TilesBrush TextureUri="ms-appx:///Assets/StoreLogo.png" />
</Border.Background>
<TextBlock Text="Tiles" />
</Border>
</Grid>
</Window>
The answer from #simon-mourier was the key for me in finally getting this done.
I created a TiledContentControl that has a ContentControl in front of the tiled background, and which reloads its bitmap image when the TileUriString property is changed (e.g. due to a binding).
There are also properties TileWidth, TileHeight to control the drawn size of the tile bitmap, as well as AlignRight and AlignBottom to make the bitmap align with the right edge or bottom edge instead of the left or top edge. The alignment parameters are useful to get a seamless continuity between two TiledContentControls that are right next to each other.
I am providing this back to the community with thanks for all of the help I've gotten on various coding challenges in the past. Note: I have done some basic testing but not extensive testing.
The key nuget packages used are Microsoft.Graphics.Win2D 1.0.4 and Microsoft.WindowsAppSDK 1.2. There are some interesting coding challenges that I discuss in a comment in the code. For example the need to prevent memory leakage when subscribing to Win2D C++ events from WinUI3 C# code.
Here is TiledContentControl.xaml:
<UserControl
x:Class="Z.Framework.TiledContentControl"
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:win2d="using:Microsoft.Graphics.Canvas.UI.Xaml"
mc:Ignorable="d"
Padding="0"
>
<Grid
RowDefinitions="*"
ColumnDefinitions="*"
>
<win2d:CanvasControl
x:Name="CanvasControl"
Grid.Row="0"
Grid.Column="0"
>
</win2d:CanvasControl>
<ContentPresenter
Name="ContentPresenter"
Grid.Row="0"
Grid.Column="0"
Background="Transparent"
Foreground="{x:Bind Foreground, Mode=OneWay}"
HorizontalContentAlignment="{x:Bind HorizontalContentAlignment, Mode=OneWay}"
VerticalContentAlignment="{x:Bind VerticalContentAlignment, Mode=OneWay}"
Padding="{x:Bind Padding, Mode=OneWay}"
Content="{x:Bind Content, Mode=OneWay}"
>
</ContentPresenter>
</Grid>
</UserControl>
Here is TiledContentControl.xaml.cs:
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.UI;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
using System;
using System.Diagnostics;
using System.Numerics;
using System.Threading.Tasks;
using Windows.Foundation;
namespace Z.Framework
{
/// <summary>
/// A control that has a tiled (repeating) bitmap background behind a content control.
///
/// Setting the TileUriString will change the tiled bitmap. Setting the drawing parameters
/// (TileWidth, TileHeight, AlignRight, AlignBottom) will scale the bitmap or offset it so
/// that it is right or bottom aligned.
/// </summary>
[ContentProperty(Name="Content")]
public sealed partial class TiledContentControl : UserControl
{
#region Discussion
// There are a number of necessary objectives to achieve the Win2D tiling with post-Load updates.
// Goal: to trigger an async load-resources when a resource-related property of the control
// changes. This is accomplished by calling StartLoadingResources when the TileUriString changes.
// Goal: cancel any resource loads that are in progress when the new load is requested.
// This is done in StartNewLoadResourcesTaskAndCleanupOldTaskAsync.
// To do it, one must store the resource-loading task (LoadResourcesTask).
// Goal: to store the resources that have been loaded, and dispose them timely.
// The LoadResourcesTask contains the loaded resources in the Result property.
// They are kept around indefinitely, except if we start a new resource load task
// then any resources in the old load task are disposed. Also, when loading several
// resources, if one of the resource loads fails then we dispose of the others.
// The CanvasResourcesRecord and LoadResourcesAsync provide a generalizable way of
// storing resources in the task result.
// Goal: make sure that any exceptions from resource creation are thrown to Win2D, so that
// Win2D can handle device-lost events (which includes Win2D triggering a new CreateResources).
// It is accomplished by only throwing load-resource exceptions from the Win2d draw handler.
// Goal: prevent Draw from being called before resources are loaded. Resource loads that are
// triggered by Win2D go through the CreateResources event handler, allowing the use of
// CanvasCreateResourcesEventArgs.TrackAsyncAction which will postpone the Draw call -- not
// until the resources are loaded but at least while the load task is started. A Draw
// callback may then occur before the load completes, but then when the load completes
// it will invalidate the CanvasControl and another Draw callback will occur.
// It does not appear to be necessary from a Win2D perspective to prevent Draw calls
// while subsequent (post-CreateResources) resource loads are being done.
// Goal: to prevent memory leaks due to .NET not being able to detect the reference cycle
// between the main control and the CanvasControl. This is accomplished by only subscribing
// to CanvasControl events while the main control is loaded.
// References:
// https://microsoft.github.io/Win2D/WinUI2/html/M_Microsoft_Graphics_Canvas_UI_CanvasCreateResourcesEventArgs_TrackAsyncAction.htm
// https://stackoverflow.com/questions/74527783/repeating-brush-or-tile-of-image-in-winui-3-composition-api
// https://microsoft.github.io/Win2D/WinUI2/html/RefCycles.htm
// https://english.r2d2rigo.es/
// https://microsoft.github.io/Win2D/WinUI3/html/M_Microsoft_Graphics_Canvas_UI_CanvasCreateResourcesEventArgs_TrackAsyncAction.htm
// https://learn.microsoft.com/en-us/windows/win32/direct2d/tile
#endregion
#region ctor
public TiledContentControl()
{
this.InitializeComponent();
this.Loaded += this.TiledContentControl_Loaded; // OK, same lifetime
this.Unloaded += this.TiledContentControl_Unloaded; // OK, same lifetime
}
private void TiledContentControl_Loaded(object sender, RoutedEventArgs e)
{
this.CanvasControl.Draw += this.CanvasControl_Draw; // OK, matched in Unloaded
this.CanvasControl.CreateResources += this.CanvasControl_CreateResources;
}
private void TiledContentControl_Unloaded(object sender, RoutedEventArgs e)
{
this.CanvasControl.Draw -= this.CanvasControl_Draw;
this.CanvasControl.CreateResources -= this.CanvasControl_CreateResources;
}
#endregion
#region CanvasResourcesRecord, LoadResourcesAsync, LoadResourcesTask
private record class CanvasResourcesRecord(
CanvasBitmap TileBitmap,
CanvasImageBrush TileBrush
): IDisposable
{
public void Dispose()
{
this.TileBitmap.Dispose();
this.TileBrush.Dispose();
}
}
static private async Task<CanvasResourcesRecord> LoadResourcesAsync(CanvasControl canvasControl, string tileUriString)
{
object[] resources = new object[2];
try {
Uri tileUri = new Uri(tileUriString);
Task<CanvasBitmap> loadTileBitmap = CanvasBitmap.LoadAsync(canvasControl, tileUri).AsTask();
CanvasBitmap tileBitmap = await loadTileBitmap;
resources[0] = tileBitmap;
CanvasImageBrush tileBrush = new CanvasImageBrush(canvasControl, tileBitmap);
tileBrush.ExtendX = CanvasEdgeBehavior.Wrap;
tileBrush.ExtendY = CanvasEdgeBehavior.Wrap;
resources[1] = tileBrush;
} catch {
// Cleanup from partial/incomplete creation
foreach (object? resource in resources) {
(resource as IDisposable)?.Dispose();
}
throw;
}
canvasControl.Invalidate(); // now that resources are loaded, we trigger an async Draw.
return new CanvasResourcesRecord(
TileBitmap: (CanvasBitmap)resources[0],
TileBrush: (CanvasImageBrush)resources[1]
);
}
private Task<CanvasResourcesRecord>? LoadResourcesTask {
get { return this._loadResourcesTask; }
set { this._loadResourcesTask = value; }
}
private Task<CanvasResourcesRecord>? _loadResourcesTask;
#endregion
#region CanvasControl_CreateResources
private void CanvasControl_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args)
{
Debug.Assert(sender == this.CanvasControl);
args.TrackAsyncAction(this.StartNewLoadResourcesTaskAndCleanupOldTaskAsync().AsAsyncAction());
}
#endregion
#region StartLoadingResources, StartNewLoadResourcesTaskAndCleanupOldTaskAsync
private void StartLoadingResources()
{
if (this.CanvasControl.IsLoaded) {
Task _ = this.StartNewLoadResourcesTaskAndCleanupOldTaskAsync();
}
}
private async Task StartNewLoadResourcesTaskAndCleanupOldTaskAsync()
{
// Start new task, if the necessary input properties are available.
string? tileUriString = this.TileUriString;
Task<CanvasResourcesRecord>? oldTask = this.LoadResourcesTask;
if (tileUriString != null) {
this.LoadResourcesTask = LoadResourcesAsync(this.CanvasControl, tileUriString);
} else {
this.LoadResourcesTask = null;
}
// Cleanup old task.
if (oldTask != null) {
oldTask.AsAsyncAction().Cancel();
try {
await oldTask;
} catch {
// ignore exceptions from the cancelled task
} finally {
if (oldTask.IsCompletedSuccessfully) {
oldTask.Result.Dispose();
}
}
}
}
#endregion
#region CanvasControl_Draw, ActuallyDraw
private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
Debug.Assert(sender == this.CanvasControl);
if (!this.DrawingParameters.AreFullyDefined) { return; }
if (!this.DrawingParameters.AreValid) { throw new InvalidOperationException($"Invalid drawing parameters (typically width or height)."); }
Task<CanvasResourcesRecord>? loadResourcesTask = this.LoadResourcesTask;
if (loadResourcesTask == null) { return; }
if (loadResourcesTask.IsCompletedSuccessfully) {
CanvasResourcesRecord canvasResources = loadResourcesTask.Result;
this.ActuallyDraw( args, canvasResources);
} else if (loadResourcesTask.IsFaulted) {
// Throw exceptions to Win2D, for example DeviceLostException resulting in new CreateResoures event
loadResourcesTask.Exception?.Handle(e => throw e);
} else {
return;
}
}
private void ActuallyDraw( CanvasDrawEventArgs args, CanvasResourcesRecord canvasResources)
{
Debug.Assert(this.DrawingParameters.AreFullyDefined && this.DrawingParameters.AreValid);
Debug.Assert(this.DrawingParameters.AlignRight != null && this.DrawingParameters.AlignBottom != null);
CanvasControl canvasControl = this.CanvasControl;
float scaleX = (float)(this.DrawingParameters.TileWidth / canvasResources.TileBitmap.Bounds.Width);
float scaleY = (float)(this.DrawingParameters.TileHeight / canvasResources.TileBitmap.Bounds.Height);
float translateX = ((bool)this.DrawingParameters.AlignRight) ? (float)((canvasControl.RenderSize.Width % this.DrawingParameters.TileWidth) - this.DrawingParameters.TileWidth) : (float)0;
float translateY = ((bool)this.DrawingParameters.AlignBottom) ? (float)((canvasControl.RenderSize.Height % this.DrawingParameters.TileHeight) - this.DrawingParameters.TileHeight) : (float)0;
Matrix3x2 transform = Matrix3x2.CreateScale( scaleX, scaleY);
transform.Translation = new Vector2(translateX, translateY);
canvasResources.TileBrush.Transform = transform;
Rect rectangle = new Rect(new Point(), canvasControl.RenderSize);
args.DrawingSession.FillRectangle(rectangle, canvasResources.TileBrush);
}
#endregion
#region Content
new public UIElement? Content {
get { return (UIElement?)this.GetValue(ContentProperty); }
set { this.SetValue(ContentProperty, value); }
}
new public static DependencyProperty ContentProperty { get; } = DependencyProperty.Register(nameof(TiledContentControl.Content), typeof(UIElement), typeof(TiledContentControl), new PropertyMetadata(default(UIElement)));
#endregion
#region TileUriString
public string? TileUriString {
get { return (string?)this.GetValue(TileUriStringProperty); }
set { this.SetValue(TileUriStringProperty, value); }
}
public static readonly DependencyProperty TileUriStringProperty = DependencyProperty.Register(nameof(TiledContentControl.TileUriString), typeof(string), typeof(TiledContentControl), new PropertyMetadata(default(string), new PropertyChangedCallback(OnTileUriStringChanged)));
private static void OnTileUriStringChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
TiledContentControl #this = (TiledContentControl)sender;
#this.StartLoadingResources();
}
#endregion
#region TileWidth, TileHeight, AlignRight, AlignBottom; OnDrawingParameterChanged, DrawingParametersRecord, DrawingParameters
public double TileWidth {
get { return (double)this.GetValue(TileWidthProperty); }
set { this.SetValue(TileWidthProperty, value); }
}
public static readonly DependencyProperty TileWidthProperty = DependencyProperty.Register(nameof(TileWidth), typeof(double), typeof(TiledContentControl), new PropertyMetadata(double.NaN, new PropertyChangedCallback(OnDrawingParameterChanged)));
public double TileHeight {
get { return (double)this.GetValue(TileHeightProperty); }
set { this.SetValue(TileHeightProperty, value); }
}
public static readonly DependencyProperty TileHeightProperty = DependencyProperty.Register(nameof(TileHeight), typeof(double), typeof(TiledContentControl), new PropertyMetadata(double.NaN, new PropertyChangedCallback(OnDrawingParameterChanged)));
public bool? AlignRight {
get { return (bool?)this.GetValue(AlignRightProperty); }
set { this.SetValue(AlignRightProperty, value); }
}
public static readonly DependencyProperty AlignRightProperty = DependencyProperty.Register(nameof(AlignRight), typeof(bool?), typeof(TiledContentControl), new PropertyMetadata(default(bool?), new PropertyChangedCallback(OnDrawingParameterChanged)));
public bool? AlignBottom {
get { return (bool?)this.GetValue(AlignBottomProperty); }
set { this.SetValue(AlignBottomProperty, value); }
}
public static readonly DependencyProperty AlignBottomProperty = DependencyProperty.Register(nameof(AlignBottom), typeof(bool?), typeof(TiledContentControl), new PropertyMetadata(default(bool?), new PropertyChangedCallback(OnDrawingParameterChanged)));
private static void OnDrawingParameterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
TiledContentControl #this = (TiledContentControl)sender;
#this.DrawingParameters = new DrawingParametersRecord(#this.TileWidth, #this.TileHeight, #this.AlignRight, #this.AlignBottom);
#this.CanvasControl.Invalidate(); // trigger an async redraw using the new parameters.
}
private record struct DrawingParametersRecord(
double TileWidth,
double TileHeight,
bool? AlignRight,
bool? AlignBottom
)
{
public bool AreFullyDefined => !double.IsNaN(this.TileWidth) && !double.IsNaN(this.TileHeight) && this.AlignBottom != null && this.AlignRight != null;
public bool AreValid => this.TileWidth > 0 && this.TileHeight > 0;
}
private DrawingParametersRecord DrawingParameters { get; set; }
#endregion
}
}
I'm new C# beginer. I want to prevent throwing XAML's object by manipulation event. My app is developed in Windows Phone 8.1 RT.
I have XAML's REctangle:
<Canvas x:Name="MyCanvas" Background="White">
<Rectangle Name="TestRectangle"
Width="100" Height="200" Fill="Blue"
ManipulationMode="All"/>
</Canvas>
In MainPage:
public MainPage()
{
this.InitializeComponent();
// Handle manipulation events.
TestRectangle.ManipulationDelta += Drag_ManipulationDelta;
dragTranslation = new TranslateTransform();
TestRectangle.RenderTransform = this.dragTranslation;
this.NavigationCacheMode = NavigationCacheMode.Required;
}
void Drag_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
// Move the rectangle.
dragTranslation.X += e.Delta.Translation.X;
dragTranslation.Y += e.Delta.Translation.Y;
}
private void TestRectangle_PointerPressed(object sender,
PointerRoutedEventArgs e)
{
Rectangle rect = sender as Rectangle;
// Change the size of the Rectangle
if (null != rect)
{
rect.Width = 250;
rect.Height = 150;
}
}
private void TestRectangle_PointerReleased(object sender,
PointerRoutedEventArgs e)
{
Rectangle rect = sender as Rectangle;
// Reset the dimensions on the Rectangle
if (null != rect)
{
rect.Width = 200;
rect.Height = 100;
}
}
private void TestRectangle_PointerExited(object sender,
PointerRoutedEventArgs e)
{
Rectangle rect = sender as Rectangle;
// Finger moved out of Rectangle before the pointer exited event
// Reset the dimensions on the Rectangle
if (null != rect)
{
rect.Width = 200;
rect.Height = 100;
}
}
How to move object whitout throwing when user exit event ?
I'm assuming that by 'throwing' you mean you want to remove the inertial aspect of your rectangle? In that case remove the ManipulationMode setter from your XAML code and insert this line in your page's constructor (C#):
TestRectangle.ManipulationMode = ManipulationMode.TranslateX | ManipulationMode.TranslateY;
That should remove the intertial effect you're talking about. As a side note though, this method of moving UIElements will give you a pretty annoying lag. I faced a similar problem a little while ago and the issue was resolved by using a different approach:
Delay in drag/drop of UIElement in Windows Phone 8.1
I've implemented a Windows 8 XAML VisibilitySwitchControl that displays the first child on certain condition; otherwise the other controls are shown. The code is as follows
[ContentProperty(Name = "Items")]
public class VisibilitySwitchControl : ItemsControl
{
public VisibilitySwitchControl()
{
DefaultStyleKey = typeof(VisibilitySwitchControl);
if (Items != null)
Items.VectorChanged += OnItemsChanged;
}
public bool ShowFirst
{
get { return (bool)GetValue(ShowFirstProperty); }
set { SetValue(ShowFirstProperty, value); }
}
public static readonly DependencyProperty ShowFirstProperty =
DependencyProperty.Register("ShowFirst", typeof(bool), typeof(VisibilitySwitchControl), new PropertyMetadata(true, OnShowFirstChanged));
public object VisibleContent
{
get { return GetValue(VisibleContentProperty); }
private set { SetValue(VisibleContentProperty, value); }
}
public static readonly DependencyProperty VisibleContentProperty =
DependencyProperty.Register("VisibleContent", typeof(object), typeof(VisibilitySwitchControl), new PropertyMetadata(null));
private static void OnShowFirstChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
var visibilityItemsControl = d as VisibilitySwitchControl;
if (visibilityItemsControl != null)
{
visibilityItemsControl.Evaluate();
}
}
void OnItemsChanged(IObservableVector<object> sender, IVectorChangedEventArgs evt)
{
Evaluate();
}
void Evaluate()
{
if (Items != null && Items.Any())
{
var controls = Items.OfType<FrameworkElement>().ToList();
for (var i = 0; i < controls.Count; i++)
{
if (i == 0)
{
VisibleContent = controls[i];
controls[i].Visibility = ShowFirst ? Visibility.Visible : Visibility.Collapsed;
}
else
{
controls[i].Visibility = !ShowFirst ? Visibility.Visible : Visibility.Collapsed;
}
}
}
else
{
VisibleContent = null;
}
}
}
However, if I place two ListView controls inside my VisibilitySwitchControl the ListView can grow in way that it is larger than the page and no scrollbars are shown. It doesn't stop a the parent containers bounds.
<custom:VisibilitySwitchControl ShowFirst="{Binding Path=IsFirstLevelNav}">
<ListView x:Name="FirstListView"
VerticalAlignment="Stretch"
ItemsSource="{Binding ...}"
SelectedItem="{Binding ..., Mode=TwoWay}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
/>
<ListView x:Name="SecondListView"
VerticalAlignment="Stretch"
ItemsSource="{Binding ...}"
SelectedItem="{Binding ..., Mode=TwoWay}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
/>
</custom:VisibilitySwitchControl>
How can I enforce a VerticalAlignment="Stretch" behavior of the children? If I remove my control and place only one the lists directly in the code, everything works as expected.
Thanks for suggestions.
you want to stretch the Height of the listview try binding it to the actual height of the parent
Heres the code part you need to include
Height="{Binding ActualHeight, ElementName=parentContainer}"
where parentContainer is the name of the custom:VisibilitySwitchControl you are using . this will bind the height to the parent container's display height. Try and let me know
If what you want is that you scroll one ListView and then when you reach the end it show the second ListView then you just need to add a ScrollViewer around the ItemPresenter inside the style of VisibilitySwitchControl and disable the ListView ScrollViewer. Just note that it mean that you will lost the virtualisation inside the ListView.
If what you want is each ListView taking half the screen than the easiest is probably to just set a Fix height for each items depending on Window.Current.Bounds.Height and register for Window.Current.SizeChanged to update it when the windows heigh changed (make sure to unregister it in unloaded to prevent memory leak).
An alternative which I think would be more complicated, would be to change the ItemsPanel of VisibilitySwitchControl to something else (by default it is a Stack panel so it will grow larger than the screen) like for example to a Grid in which you set as many row with star heigh as items you have (and then you will need to set the row of each item) or by creating a custom Panel.
I am trying to create a custom Pushpin for Bing Maps in my WinRT application. My problem is that I need a reference to the actual Map from my page in order to pin the icons correctly in my userControl. So for example this is my DataTemplate which gets bound to the map and works fine for the normal pushpins. For my custom userControl to position correctly I need a reference to the parent Map in the userControl.
This is my XAML:
<m:MapItemsControl x:Name="Pushpinss" ItemsSource="{Binding InventoryItems}">
<m:MapItemsControl.ItemTemplate>
<DataTemplate>
<!-- NORMAL PUSHPIN WORKS -->
<m:Pushpin>
<m:MapLayer.Position>
<m:Location Latitude="{Binding WarehouseLatitude}"
Longitude="{Binding WarehouseLongitude}" />
</m:MapLayer.Position>
</m:Pushpin>
<!-- CUSTOM CONTROL DISPLAYS BUT DOES NOT POSITION CORRECTLY BECAUSE I NEED A REFERENCE TO THE MAP-->
<View:GPSIcon Latitude="{Binding WarehouseLatitude}"
Longitude="{Binding WarehouseLongitude}"
Radius="100000"/>
<x:Arguments>
</x:Arguments>
</DataTemplate>
</m:MapItemsControl.ItemTemplate>
</m:MapItemsControl>
This is my custom control:
public sealed partial class GPSIcon : UserControl
{
private Map _map;
private const double EARTH_RADIUS_METERS = 6378137;
public GPSIcon(Map map)
{
this.InitializeComponent();
_map = map;
_map.ViewChanged += (s, e) =>
{
UpdateAccuracyCircle();
};
}
public static readonly DependencyProperty LatitudeProperty =
DependencyProperty.Register("Latitude", typeof(double), typeof(GPSIcon), new PropertyMetadata(0));
public static readonly DependencyProperty LongitudeProperty =
DependencyProperty.Register("Longitude", typeof(double), typeof(GPSIcon), new PropertyMetadata(0));
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof(double), typeof(GPSIcon), new PropertyMetadata(0));
public double Latitude
{
get { return (double)GetValue(LatitudeProperty); }
set { SetValue(LatitudeProperty, value); }
}
public double Longitude
{
get { return (double)GetValue(LongitudeProperty); }
set { SetValue(LongitudeProperty, value); }
}
/// <summary>
/// Radius in Metres
/// </summary>
public double Radius
{
get { return (double)GetValue(RadiusProperty); }
set
{
SetValue(RadiusProperty, value);
UpdateAccuracyCircle();
}
}
private void UpdateAccuracyCircle()
{
if (_map != null && Radius >= 0)
{
double groundResolution = Math.Cos(_map.Center.Latitude * Math.PI / 180) * 2 * Math.PI * EARTH_RADIUS_METERS / (256 * Math.Pow(2, _map.ZoomLevel));
double pixelRadius = Radius / groundResolution;
AccuracyCircle.Width = pixelRadius;
AccuracyCircle.Height = pixelRadius;
AccuracyCircle.Margin = new Thickness(-pixelRadius / 2, -pixelRadius / 2, 0, 0);
}
}
}
Is this possible at all? I have also tried using the x:Arguments directive as described here:
http://msdn.microsoft.com/en-us/library/ee795382.aspx
Thanks
UPDATE 1
Do following changes
1) Add empty constructor.
public GPSIcon()
{
this.InitializeComponent();
}
2) Declare DP of type Map
public Map MyMap
{
get { return (Map)GetValue(MyMapProperty); }
set { SetValue(MyMapProperty, value); }
}
public static readonly DependencyProperty MyMapProperty =
DependencyProperty.Register("MyMap", typeof(Map), typeof(GPSIcon), new PropertyMetadata(default(Map), OnMapSet));
private static void OnMapSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
_map = ((GPSIcon)(d)).MyMap;
_map.ViewChanged += (ss, ee) =>
{
((GPSIcon)(d)).UpdateAccuracyCircle();
};
}
3) Pass Map object like this in XAML
<m:Map x:Name="objMap">
<m:MapItemsControl x:Name="Pushpinss" ItemsSource="{Binding InventoryItems}">
<m:MapItemsControl.ItemTemplate>
<DataTemplate>
<View:GPSIcon Latitude="{Binding WarehouseLatitude}"
Longitude="{Binding WarehouseLongitude}"
Radius="100000"
MyMap="{Binding ElementName=objMap}"/>
</DataTemplate>
</m:MapItemsControl.ItemTemplate>
</m:MapItemsControl>
</m:Map>
Declare one more dependency property of type Map and then you should pass current map instance as a value of that DP in <View:GPSIcon ... />
Simply, you need to follow the same logic as how to Pass parameter to constructor from xaml in Silverlight
To get your custom UIElement to position properly on the map what you can do instead of doing this in code is simply set the position of the UIElement the same way you set the position of a pushpin.
For example:
<View:GPSIcon Radius="100000">
<m:MapLayer.Position>
<m:Location Latitude="{Binding WarehouseLatitude}"
Longitude="{Binding WarehouseLongitude}" />
</m:MapLayer.Position>
</View:GPSIcon>
I have a TextBlock with the following setting:
TextWrapping="Wrap"
Can I determine the maximum number of lines?
for example consider the following string TextBlock.Text:
This is a very good horse under the blackboard!!
It currently has been shows like this:
This is a very
good horse under
the blackboard!!
I need that to become something like:
This is a very
good horse ...
any solution?
Update (for UWP)
In UWP Apps you don't need this and can use the TextBlock property MaxLines (see MSDN)
Original Answer:
If you have a specific LineHeight you can calculate the maximum height for the TextBlock.
Example:
TextBlock with maximum 3 lines
<TextBlock
Width="300"
TextWrapping="Wrap"
TextTrimming="WordEllipsis"
FontSize="24"
LineStackingStrategy="BlockLineHeight"
LineHeight="28"
MaxHeight="84">YOUR TEXT</TextBlock>
This is all that you need to get your requirement working.
How to do this dynamically?
Just create a new control in C#/VB.NET that extends TextBlock and give it a new DependencyProperty int MaxLines.
Then override the OnApplyTemplate() method and set the MaxHeight based on the LineHeight * MaxLines.
That's just a basic explanation on how you could solve this problem!
Based tobi.at's and gt's answer I have created this MaxLines behaviour. Crucially it doesn't depend upon setting the LineHeight property by calculating the line height from the font. You still need to set TextWrapping and TextTrimming for it the TextBox to be render as you would like.
<TextBlock behaviours:NumLinesBehaviour.MaxLines="3" TextWrapping="Wrap" TextTrimming="CharacterEllipsis" Text="Some text here"/>
There in also a MinLines behaviour which can be different or set to the same number as the MaxLines behaviour to set the number of lines.
public class NumLinesBehaviour : Behavior<TextBlock>
{
TextBlock textBlock => AssociatedObject;
public static readonly DependencyProperty MaxLinesProperty =
DependencyProperty.RegisterAttached(
"MaxLines",
typeof(int),
typeof(NumLinesBehaviour),
new PropertyMetadata(default(int), OnMaxLinesPropertyChangedCallback));
public static void SetMaxLines(DependencyObject element, int value)
{
element.SetValue(MaxLinesProperty, value);
}
public static int GetMaxLines(DependencyObject element)
{
return (int)element.GetValue(MaxLinesProperty);
}
private static void OnMaxLinesPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock element = d as TextBlock;
element.MaxHeight = getLineHeight(element) * GetMaxLines(element);
}
public static readonly DependencyProperty MinLinesProperty =
DependencyProperty.RegisterAttached(
"MinLines",
typeof(int),
typeof(NumLinesBehaviour),
new PropertyMetadata(default(int), OnMinLinesPropertyChangedCallback));
public static void SetMinLines(DependencyObject element, int value)
{
element.SetValue(MinLinesProperty, value);
}
public static int GetMinLines(DependencyObject element)
{
return (int)element.GetValue(MinLinesProperty);
}
private static void OnMinLinesPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock element = d as TextBlock;
element.MinHeight = getLineHeight(element) * GetMinLines(element);
}
private static double getLineHeight(TextBlock textBlock)
{
double lineHeight = textBlock.LineHeight;
if (double.IsNaN(lineHeight))
lineHeight = Math.Ceiling(textBlock.FontSize * textBlock.FontFamily.LineSpacing);
return lineHeight;
}
}
If you have Height, TextWrapping, and TextTrimming all set, it will behave exactly like you want:
<TextBlock Height="60" FontSize="22" FontWeight="Thin"
TextWrapping="Wrap" TextTrimming="CharacterEllipsis">
The above code will wrap up to two lines, then use CharacterEllipsis beyond that point.
you need TextTrimming="WordEllipsis" setting in your TextBlock
Based on #artistandsocial's answer, I created a attached property to set the maximum number of lines programatically (rather than having to overload TextBlock which is discouraged in WPF).
public class LineHeightBehavior
{
public static readonly DependencyProperty MaxLinesProperty =
DependencyProperty.RegisterAttached(
"MaxLines",
typeof(int),
typeof(LineHeightBehavior),
new PropertyMetadata(default(int), OnMaxLinesPropertyChangedCallback));
public static void SetMaxLines(TextBlock element, int value) => element.SetValue(MaxLinesProperty, value);
public static int GetMaxLines(TextBlock element) =>(int)element.GetValue(MaxLinesProperty);
private static void OnMaxLinesPropertyChangedCallback(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (d is TextBlock textBlock)
{
if (textBlock.IsLoaded)
{
SetLineHeight();
}
else
{
textBlock.Loaded += OnLoaded;
void OnLoaded(object _, RoutedEventArgs __)
{
textBlock.Loaded -= OnLoaded;
SetLineHeight();
}
}
void SetLineHeight()
{
double lineHeight =
double.IsNaN(textBlock.LineHeight)
? textBlock.FontFamily.LineSpacing * textBlock.FontSize
: textBlock.LineHeight;
textBlock.MaxHeight = Math.Ceiling(lineHeight * GetMaxLines(textBlock));
}
}
}
}
By default, the LineHeight is set to double.NaN, so this value must first be set manually, otherwise a height is calculated from the FontFamily and FontSize of the TextBlock.
The attached property MaxLines and other relevant properties can then be set in a Style:
<Style TargetType="{x:Type TextBlock}"
BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="TextTrimming"
Value="CharacterEllipsis" />
<Setter Property="TextWrapping"
Value="Wrap" />
<Setter Property="LineHeight"
Value="16" />
<Setter Property="LineStackingStrategy"
Value="BlockLineHeight" />
<Setter Property="behaviors:LineHeightBehavior.MaxLines"
Value="2" />
</Style>
For anybody developing UWP or WinRT Applications, TextBlock has a MaxLines property you can set.
I doubt that is configurable, Wrapping is based on a number of factors such as font-size/kerning, available width of the textblock (horizontalalignment=stretch can make a big difference), parent's panel type (scrollviewer/stackpanel/grid) etc.
If you want the text to flow to the next line explicitly you should use "Run" blocks instead and then use wrapping of type ellipses for that run block.