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>
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 developing with uwp and i've a problem with data binding. I have a listView that i fill with a custom panel elements called PlaylistLeftOption class. This class inherit Panel class attributes that inherit FrameworkElement class attribute and its methods so i have a SetBinding method avaible.
Now i'm trying to bind the height value (it's equal to other elements) so i created a static attribute, called PerformanceItemHeight, in other extern singleton class.
since i need to fill listview dinamically i'm trying to bind the value inside the constructor but it don't work.
This is the code inside constructor:
public PlaylistLeftOption()
{
mainGrid.Background = new SolidColorBrush(Colors.Red);
mainGrid.BorderBrush = new SolidColorBrush(Colors.Black);
mainGrid.BorderThickness = new Thickness(0.5,0.25,0.5,0.25);
WidthVal = 200;
HeightVal = 50;
var myBinding = new Binding();
myBinding.Source = PerformanceLayout.Instance.PerformanceItemHeight;
myBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
myBinding.Mode = BindingMode.TwoWay;
SetBinding(HeightValProperty, myBinding);
Children.Add(mainGrid);
}
And this is the property:
public static readonly DependencyProperty HeightValProperty = DependencyProperty.Register(
"HeightVal",
typeof(double),
typeof(PlaylistLeftOption),
new PropertyMetadata(50)
);
public double HeightVal
{
get => (double)GetValue(HeightValProperty);
set
{
SetValue(HeightValProperty, value);
Height = HeightVal;
mainGrid.Height = HeightVal;
globalSize.Height = HeightVal;
}
}
This is the code for PerformanceItemHeight:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private double _performanceItemHeight = 50;
public double PerformanceItemHeight {
get => _performanceItemHeight;
set {
_performanceItemHeight = value;
this.OnPropertyChanged();
}
}
Why does via xaml it works?
i tryied to add PlaylistLeftOption item inside listview via xaml and it's ok!
thank you
By testing, the binding of HeightVal works in XAML and the binding of HeightVal does not work in code-behind. You could see the reason in the section Implementing the wrapper of the document Custom dependency properties which says that your wrapper implementations should perform only the GetValue and SetValue operations. Otherwise, you'll get different behavior when your property is set via XAML versus when it is set via code.
You could add a property-changed callback method to notify the changes of HeightVal actively.
For example:
public static readonly DependencyProperty HeightValProperty = DependencyProperty.Register(
"HeightVal",
typeof(double),
typeof(PlaylistLeftOption),
new PropertyMetadata(100, new PropertyChangedCallback(OnHeightValChanged))
);
private static void OnHeightValChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PlaylistLeftOption playlistLeftOption = d as PlaylistLeftOption;
if(playlistLeftOption != null)
{
var height = (Double)e.NewValue;
playlistLeftOption.HeightVal = height;
}
}
And change the binging code like this:
var myBinding = new Binding();
myBinding.Source = PerformanceLayout.Instance;
myBinding.Path = new PropertyPath("PerformanceItemHeight");
myBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
myBinding.Mode = BindingMode.TwoWay;
SetBinding(HeightValProperty, myBinding);
I am using BadgeView plugin in Xamarin.Forms with TitleView but getting exception
Xamarin.Forms.Xaml.XamlParseException: Position 8:81. Multiple
properties with name 'BadgeView.Shared.CircleView.CornerRadius' found.
This is my code
<NavigationPage.TitleView>
<StackLayout Orientation="Horizontal" VerticalOptions="End" Spacing="10">
<Image Source="bell.png" HorizontalOptions="Center" VerticalOptions="Center"></Image>
<badge:BadgeView Text="2" BadgeColor="Yellow" VerticalOptions="End" HorizontalOptions="Start"></badge:BadgeView>
</StackLayout>
</NavigationPage.TitleView>
This is a bug in the plugin which is marked in the issues section in the plugin's Github
And there is a workaround that one guy has added but I am not sure if that would work for you, as shown here what he did was
CircleView.cs
Note: Comment the CornerRadius property.
public class CircleView : BoxView
{
//public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CornerRadius), typeof(double), typeof(CircleView), 0.0);
//public double CornerRadius
//{
// get { return (double)GetValue(CornerRadiusProperty); }
// set { SetValue(CornerRadiusProperty, value); }
//}
}
For Android:
CircleViewRenderer.cs
Note: Added hard-coded value for CornerRadius (16)
public class CircleViewRenderer : BoxRenderer
{
private float _cornerRadius;
private RectF _bounds;
private Path _path;
public CircleViewRenderer(Context context)
: base(context)
{
}
public static void Initialize() { }
protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
{
base.OnElementChanged(e);
if (Element == null)
{
return;
}
var element = (CircleView)Element;
_cornerRadius = TypedValue.ApplyDimension(ComplexUnitType.Dip, (float)16, Context.Resources.DisplayMetrics);
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
if (w != oldw && h != oldh)
{
_bounds = new RectF(0, 0, w, h);
}
_path = new Path();
_path.Reset();
_path.AddRoundRect(_bounds, _cornerRadius, _cornerRadius, Path.Direction.Cw);
_path.Close();
}
public override void Draw(Canvas canvas)
{
canvas.Save();
canvas.ClipPath(_path);
base.Draw(canvas);
canvas.Restore();
}
}
For iOS:
CircleViewRenderer.cs
Note: Added hard-coded value for CornerRadius (16)
iOS code is not tested yet.
public class CircleViewRenderer : BoxRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
Layer.MasksToBounds = true;
//Layer.CornerRadius = (float)((CircleView)Element).CornerRadius / 2.0f;
Layer.CornerRadius = (float)(16) / 2.0f;
}
}
I created a simple Rating user control, the problem this control won't in WinRT work when I use binding, it works fine on windows phone, This is my Control:
public sealed partial class RatingControl : UserControl
{
public int Rate { get { return (int)GetValue(RateProperty); } set { SetValue(RateProperty, value); } }
public static readonly DependencyProperty RateProperty = DependencyProperty.Register("Rate",
typeof(int),
typeof(RatingControl), null);
public RatingControl()
{
this.InitializeComponent();
this.Loaded += RatingControl_Loaded;
}
void RatingControl_Loaded(object sender, RoutedEventArgs e)
{
List<Image> Images = new List<Image>();
for (int i = 0; i < 5; i++)
{
Image img = new Image { Width = 35, Height = 35, Margin = new Thickness(3) };
img.Source = new BitmapImage { UriSource = new System.Uri("ms-appx:Images/Stars/notFilled.png") };
Images.Add(img);
sp.Children.Add(img);
}
for (int i = 0; i < Rate; i++)
Images[i].Source = new BitmapImage { UriSource = new System.Uri("ms-appx:Images/Stars/Filled.png") };
}
}
When I hardcode the value, it works fine:
<local:RatingControl Rate="3" />
but when I use Binding, it just shows zero stars. I checked the value of Rate, it is always zero.
<local:RatingControl Rate="{Binding Decor, Mode=TwoWay}" />
UPDATE: I just found out that the binding happens before I get the value of the Rate, so its zero all the time. How can I fix that? I need the binding to happens after I get the value. Also I thought the Binding happens everytime I change the Rate value.
SOLUTION: I Didnt implement the DependencyObject right, I should've done this:
public static readonly DependencyProperty RateProperty = DependencyProperty.Register("Rate",
typeof(int),
typeof(RatingControl), new PropertyMetadata(0, new PropertyChangedCallback(BindRateControl)));
SOLUTION: I Didnt implement the DependencyObject right, I should've done this (adding a callback method):
public static readonly DependencyProperty RateProperty = DependencyProperty.Register("Rate",
typeof(int),
typeof(RatingControl),
new PropertyMetadata(0, new PropertyChangedCallback(BindRateControl)));
has you try adding the UserControl from code-behind. this help you to ensure that the UserControl is triggered after getting the value.
Is there any xaml serialization attribute that I can specify for a dependency property which actually is a collection (TextDecorationCollection)?
I want to use serialization for cloning a very large and complex object. Here a sample of the code, simplified:
There is a MyVisualObject, that contains a lot of properties, including a custom font, which I want to clone
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class Export : Attribute
{
}
public class MyVisualObject : DependencyObject
{
[Export]
public CustomFont Font
{
get { return (CustomFont)GetValue(FontProperty); }
set { SetValue(FontProperty, value); }
}
// Using a DependencyProperty as the backing store for Font. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontProperty =
DependencyProperty.Register("Font", typeof(CustomFont), typeof(MyVisualObject));
public MyVisualObject()
{
this.Font = new CustomFont();
}
}
And the custom font is defined like this:
public class CustomFont : DependencyObject
{
public TextDecorationCollection Decorations
{
get { return (TextDecorationCollection)GetValue(DecorationsProperty); }
set { SetValue(DecorationsProperty, value); }
}
// Using a DependencyProperty as the backing store for TextDecorations. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DecorationsProperty =
DependencyProperty.Register("Decorations", typeof(TextDecorationCollection), typeof(CustomFont), new UIPropertyMetadata(new TextDecorationCollection()));
public CustomFont()
{
this.Decorations = System.Windows.TextDecorations.Underline;
}
}
THe deep clone method:
public static T DeepClone<T>(T from)
{
object clone = Activator.CreateInstance(from.GetType());
Type t = from.GetType();
System.Reflection.PropertyInfo[] pinf = t.GetProperties();
foreach (PropertyInfo p in pinf)
{
bool serialize = false;
foreach (object temp in p.GetCustomAttributes(true))
{
if (temp is Export)
{
serialize = true;
}
}
if (serialize)
{
string xaml = XamlWriter.Save(p.GetValue(from, null));
XmlReader rd = XmlReader.Create(new StringReader(xaml));
p.SetValue(clone, XamlReader.Load(rd), null);
}
}
return (T)clone;
}
The problem is that each time I initialize the Decorations as Underline
this.Decorations = System.Windows.TextDecorations.Underline;
the cloning process crashes with this error:'Add value to collection of type 'System.Windows.TextDecorationCollection' threw an exception.' Line number '1' and line position '213'.
As far as I found out, the serialization, which is this part
string xaml = XamlWriter.Save(p.GetValue(from, null));
returns an xaml which does not have the decorations set as a collection:
<CustomFont xmlns="clr-namespace:WpfApplication1;assembly=WpfApplication1" xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<CustomFont.Decorations>
<av:TextDecoration Location="Underline" />
</CustomFont.Decorations>
</CustomFont>
But the clone process would work if the xaml would be like this:
<CustomFont xmlns="clr-namespace:WpfApplication1;assembly=WpfApplication1" xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<CustomFont.Decorations>
<av:TextDecorationCollection>
<av:TextDecoration Location="Underline" />
</av:TextDecorationCollection>
</CustomFont.Decorations>
</CustomFont>
I found a workaround, something with string replacements:
xaml = xaml.Replace("<CustomFont.Decorations><av:TextDecoration Location=\"Underline\" /></CustomFont.Decorations>", "<CustomFont.Decorations><av:TextDecorationCollection><av:TextDecoration Location=\"Underline\" /></av:TextDecorationCollection></CustomFont.Decorations>");
but I think it's really dirty, and I would apreciate it if you could provide a more clean solution (specifying an attribute for the Decorations property for example)
Have you tried applying the following attribute to the Decorations property:
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]