XmlDataProvider and XPath bindings don't allow default namespace of XML data? - xaml

I am struggling to work out how to use default namespaces with XmlDataProvider and XPath bindings.
There's an ugly answer using local-name <Binding XPath="*[local-name()='Name']" /> but that is not acceptable to the client who wants this XAML to be highly maintainable.
The fallback is to force them to use non-default namespaces in the report XML but that is an undesirable solution.
The XML report file looks like the following. It will only work if I remove xmlns="http://www.acme.com/xml/schemas/report so there is no default namespace.
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type='text/xsl' href='PreviewReportImages.xsl'?>
<Report xsl:schemaLocation="http://www.acme.com/xml/schemas/report BlahReport.xsd" xmlns:xsl="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.acme.com/xml/schemas/report">
<Service>Muncher</Service>
<Analysis>
<Date>27 Apr 2010</Date>
<Time>0:09</Time>
<Authoriser>Service Centre Manager</Authoriser>
Which I am presenting in a window with XAML:
<Window x:Class="AcmeTest.ReportPreview"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ReportPreview" Height="300" Width="300" >
<Window.Resources>
<XmlDataProvider x:Key="Data"/>
</Window.Resources>
<StackPanel Orientation="Vertical" DataContext="{Binding Source={StaticResource Data}, XPath=Report}">
<TextBlock Text="{Binding XPath=Service}"/>
</StackPanel>
</Window>
with code-behind used to load an XmlDocument into the XmlDataProvider (seems the only way to have loading from a file or object varying at runtime).
public partial class ReportPreview : Window
{
private void InitXmlProvider(XmlDocument doc)
{
XmlDataProvider xd = (XmlDataProvider)Resources["Data"];
xd.Document = doc;
}
public ReportPreview(XmlDocument doc)
{
InitializeComponent();
InitXmlProvider(doc);
}
public ReportPreview(String reportPath)
{
InitializeComponent();
var doc = new XmlDocument();
doc.Load(reportPath);
InitXmlProvider(doc);
}
}

I hadn't realised that I don't need to add a prefix to the client XML data, just use a prefix in my XPath expressions that maps to the same URI as the default namespace (obvious when I slept on it!).
So, the fix was to add a namespace mapping as shown here, note the use of the r: prefix on the elements.
<Window x:Class="AcmeTest.ReportPreview"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ReportPreview" Height="300" Width="300" >
<Window.Resources>
<XmlDataProvider x:Key="Data">
<XmlDataProvider.XmlNamespaceManager>
<XmlNamespaceMappingCollection>
<XmlNamespaceMapping
Uri="http://www.acme.com/xml/schemas/report"
Prefix="r" />
</XmlNamespaceMappingCollection>
</XmlDataProvider.XmlNamespaceManager>
</XmlDataProvider>
</Window.Resources>
<StackPanel Orientation="Vertical" DataContext="{Binding Source={StaticResource Data}, XPath=Report}">
<TextBlock Text="{Binding XPath=r:Service}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding XPath=r:Name/r:Last}"/>
</StackPanel>
</Window>

Related

WinUI3: Why does the top area of the NavigationView look different?

The default appearance of the NavigationView in WinUI3 Gallery or an app created with a template studio has a space at the top. However, it looks different in apps created with Visual Studio default templates.I don't think it's controlled by the ViewModel or anything else. Why does it look different?
<!--In Template studio or WinUI3 Gallery-->
<Page>
<Grid>
<NavigationView PaneDisplayMode="LeftCompact"/>
</Grid>
</Page>
<!--In My App created with Visual Studio default templates-->
<Page>
<Grid>
<NavigationView PaneDisplayMode="LeftCompact"/>
</Grid>
</Page>
In Template studio or WinUI3 Gallery
In My App created with Visual Studio default templates
Even if you modify the ShellPage of an app created with a Template Studio as follows, there is still a difference in appearance.
public sealed partial class ShellPage : Page
{
public ShellPage()
{
InitializeComponent();
}
}
<Page
x:Class="TemplateStudioApp.Views.ShellPage"
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">
<NavigationView PaneDisplayMode="LeftCompact"/>
</Page>
That space at the top is actually the AppTitleBar. This code should create the same look.
App.xaml
<Application
x:Class="WinUI3App.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WinUI3App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
<SolidColorBrush
x:Key="WindowCaptionBackground"
Color="Transparent" />
<SolidColorBrush
x:Key="WindowCaptionBackgroundDisabled"
Color="Transparent" />
<Thickness x:Key="NavigationViewContentMargin">0,48,0,0</Thickness>
</ResourceDictionary>
</Application.Resources>
</Application>
MainWindow.xaml.cs
<Window
x:Class="WinUI3App.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:local="using:WinUI3App"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid
x:Name="AppTitleBar"
Height="{Binding ElementName=NavigationViewControl, Path=CompactPaneLength}"
VerticalAlignment="Top"
Canvas.ZIndex="1"
IsHitTestVisible="True">
<Image
Width="16"
Height="16"
HorizontalAlignment="Left"
Source="/Assets/WindowIcon.ico" />
<TextBlock
x:Name="AppTitleBarText"
Margin="28,0,0,0"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
TextWrapping="NoWrap" />
</Grid>
<NavigationView
x:Name="NavigationViewControl"
Canvas.ZIndex="0"
DisplayModeChanged="NavigationView_DisplayModeChanged"
ExpandedModeThresholdWidth="1280"
IsBackButtonVisible="Visible"
IsSettingsVisible="True" />
</Grid>
</Window>
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace WinUI3App;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
ExtendsContentIntoTitleBar = true;
SetTitleBar(this.AppTitleBar);
}
private void NavigationView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
{
AppTitleBar.Margin = new Thickness()
{
Left = sender.CompactPaneLength * (sender.DisplayMode == NavigationViewDisplayMode.Minimal ? 2 : 1),
Top = AppTitleBar.Margin.Top,
Right = AppTitleBar.Margin.Right,
Bottom = AppTitleBar.Margin.Bottom
};
}
}
<Thickness x:Key="NavigationViewContentGridBorderThickness">1,1,0,0</Thickness>
<CornerRadius x:Key="NavigationViewContentGridCornerRadius">8,0,0,0</CornerRadius>
<Thickness x:Key="NavigationViewContentMargin">0,48,0,0</Thickness>
<Thickness x:Key="NavigationViewHeaderMargin">56,34,0,0</Thickness>
<Thickness x:Key="NavigationViewPageContentMargin">56,24,56,0</Thickness>
In Template Studio, these resources from Styles/Thickness.xaml were the solution.

Xamarin-Get String from TextField and place it in Label

I am new to Xamarin Form Application development and Want to try a simple app that will get string from textfield and place it in label by data binding.
Text field with 20 px margin from both side and vertically center.
Label will be below text field.
When typing in textField, the label will update (MVVM)
UI design will be from XAML.
Thank you.
If you are using Xamarin Forms to achieve this and using DataBinding (MVVM), first in your ViewModel (We will call it MainPageViewModel.cs) you need something like this:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SandboxForms.ViewModels
{
public class MainPageViewModel : INotifyPropertyChanged
{
private string _myTextField;
public string MyTextField
{
get { return _myTextField; }
set
{
_myTextField = value;
OnPropertyChanged(nameof(MyTextField));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then in our ContentPage (We will call this one MainPage.xaml):
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SandboxForms.Pages.MainPage"
xmlns:viewmodels="clr-namespace:SandboxForms.ViewModels;SandboxForms">
<ContentPage.BindingContext>
<viewmodels:MainPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout Padding="20">
<!-- I am applying EndAndExpand to the entry and
StartAndExpand to the label to center them each other -->
<Entry
HorizontalOptions="FillAndExpand"
VerticalOptions="EndAndExpand"
Placeholder="Write here and see the magic!!!"
Text="{Binding MyTextField}"/>
<Label
HorizontalTextAlignment="End"
HorizontalOptions="FillAndExpand"
VerticalOptions="StartAndExpand"
Text="{Binding MyTextField}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Here is a few screenshots of the results:
Application starting,
Entering text on your Entry
Hope this works for you, my best regards!
There are two approaches for data binding each of which has merits depending on the situation. The first is MVVM as mentioned previously. This works well for fields that your ViewModel should know about, such as the text in an entry field but this isn't always the case and it's important to have a complete understanding before choosing the right method for your needs.
MVVM Approach
ViewModel
public class MyPageViewModel : INotifyPropertyChanged
{
private string myTextField;
public string MyTextField
{
get { return myTextField; }
set
{
if( !myTextField.Equals( value ) )
{
myTextField = value;
OnPropertyChanged("MyTextField");
}
}
}
}
View
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SandboxForms.Pages.MainPage"
xmlns:viewmodels="clr-namespace:SandboxForms.ViewModels;SandboxForms">
<ContentPage.BindingContext>
<viewmodels:MainPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout Padding="20">
<!-- I am applying EndAndExpand to the entry and
StartAndExpand to the label to center them each other -->
<Entry
HorizontalOptions="FillAndExpand"
VerticalOptions="EndAndExpand"
Placeholder="Write here and see the magic!!!"
Text="{Binding MyTextField}"/>
<Label
HorizontalTextAlignment="End"
HorizontalOptions="FillAndExpand"
VerticalOptions="StartAndExpand"
Text="{Binding MyTextField}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
This is generally a preferred approach by most developers as opposed to mixing business logic directly in the code behind of your UI.
There are a number of helpers, and frameworks out there that you can look at if you aren't familiar with this. The following are some of the more popular ones.
MvvmHelpers - James Montemagno
Prism Library (my personal favorite)
Mvvm Cross
Mvvm Light
View Centric Approach
Sometimes it actually would violate the MVVM pattern to directly bind to a property of our ViewModel, and other times we may want to display something in our View without the need of updating a backing field in the ViewModel. As an example we can look at Xamarin's guide to data binding.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderBindingsPage"
Title="Slider Bindings Page">
<StackLayout>
<Label Text="ROTATION"
BindingContext="{x:Reference Name=slider}"
Rotation="{Binding Path=Value}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="CenterAndExpand" />
<Label BindingContext="{x:Reference slider}"
Text="{Binding Value,
StringFormat='The angle is {0:F0} degrees'}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
I should note that one of the most common times I would recommend using this approach is with Context Actions in a ListView, since our ViewModel may contain the Command that we want to execute on the individual cell, however the cell in which we are executing the context action actually is bound to the object from our IEnumerable<T> and not our ViewModel. In this particular case we would do something like the following:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns ="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Name="someListPage"
x:Class="MyApp.Views.SomeListPage">
<ListView ItemsSource="{Binding Gear}"
CachingStrategy="RecycleElement"
IsRefreshing="{Binding IsRefreshing}"
IsPullToRefreshEnabled="True"
RefreshCommand="{Binding RefreshCommand}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Description}" Detail="{Binding Detail}">
<TextCell.ContextActions>
<MenuItem Text="Remove"
Command="{Binding BindingContext.RemoveItemCommand,Source={x:Reference someListPage}}"
CommandParameter="{Binding .}"
IsDestructive="True" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
You'll notice that for this to work we first give the page itself a name that we can then reference for our binding for the ContextAction Command property. This is only changing where we are looking for this single property. We then resume using the normal binding context for the CommandParameter property and pass in the actual object the cell is bound to with {Binding .}
Hope this helps you better understand your options for binding with Xaml. Happy Coding!

Define a collection inside XAML

I want to create a Binding to a collection of strings defined inside XAML.
In WPF I could create an ArrayList as a resource with a key, ready to be used as the source of a Binding (using a StaticResource).
Is this possible in Xamarin Forms?
EDIT: I've tried with this XAML with the solution proposed by #Stephane Delcroix, but I'm getting an Unhandled Exception:
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Class="ReferenceApp.Views.GamesPage"
Title="Games">
<ContentPage.Resources>
<x:Array Type="{x:Type sys:String}" x:Key="array">
<x:String>Hello</x:String>
<x:String>World</x:String>
</x:Array>
</ContentPage.Resources>
<Grid />
</ContentPage>
However, the exception is not thrown if I remove the <x:Array >... </x:Array>
What am I doing wrong?
I see that you are using the XF standard markup extensions. Your mistake seems to be in Type="{x:Type sys:String}", instead of sys:String you should write x:String which appears in the common xmlns:x
In this sample I fill a listview with strings
<ListView Margin="10">
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Hello</x:String>
<x:String>World</x:String>
</x:Array>
</ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can use the built-in x:Array
<x:Array Type="{x:Type sys:String}" x:Key="array">
<x:String>Hello</x:String>
<x:String>World</x:String>
</x:Array>
with sys defined as xmlns:sys="clr-namespace:System;assembly=mscorlib"
or any collection you like, e.g. List
<scg:List x:TypeArguments="{x:Type sys:String}" x:Key="genericList">
<x:String>Hello</x:String>
<x:String>World</x:String>
</scg:List>
with sys defined as before, and scg being xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib"
This is an answer to the updated question.
<x:string> only supports constant values. You can address this issue with a Markup Extension
Its functionality is trivial: it returns its parameter.
using System;
using Xamarin.Forms.Xaml;
namespace YOURAPP.Extensions
{
public class StringExtension : IMarkupExtension
{
public string Value { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
return Value;
}
}
}
Use it like this in a view:
Add xmlns:ext="clr-namespace:YOURAPP.Extensions" to the root element
<ext:StringExtension Value="{anything here}" /> where you would otherwise want <x:string>
Note that this causes an Add to be called to the IEnumerable. For custom controls you would need initialization (to avoid a NullReferenceException) and and ObservableCollection to make sure that the view is updated on adding.

How to use a path data from a resource dictionary in UWP

This is trivial thing but yet it does not work.
I have something like this (it is in its own folder)
<ResourceDictionary>
<Path x:Key="Test"
Stroke="Black"
Fill="Gray"
Data="M 10,100 C 10,300 300,-200 300,100" />
</ResourceDictionary>
Now I want to use it
<Page>
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergeDictionaries>
<ResourceDictionary Source="MyFolder/MyResourceDictionary.xaml/>
</ResourceDictionary.MergeDictionaries>
</ResourceDictionary>
</Page.Resources>
<ContentPresenter Content="{StaticResource Test}"/>
<Page/>
This will throw an exception, but I don't understand why. Exactly the same scenario in wpf works fine.
What about this solution?
Declare your GeometryData
<x:String x:Key="TestPathGeomerty">M 10,100 C 10,300 300,-200 300,100</x:String>
And use Path, instead ContentPresenter
<Path Data="{StaticResource TestPathGeomerty}"
Fill="Red"/>
The Path.Data property is of type Geometry so define it as a Geometry instead of a string
<Geometry x:Key="TestPathGeomerty">M 10,100 C 10,300 300,-200 300,100</Geometry>
<Path Data="{StaticResource TestPathGeomerty}"
Fill="Red"/>
In WPF, you can share the same instance within multiple controls. Unfortunately this is not possible in UWP.
The only solution that is guaranteed to work in UWP, is to define a DataTemplate in your resource containing the icon.
It is also better to use PathIcon instead of Path. PathIcon makes use of the Foreground property that will be inherited from your parent controls.
Here's an example on how to share Data paths for icons that will automatically scale (by using a Viewbox).
<Page.Resources>
<DataTemplate x:Key="MagnifyingGlassPathIconCT">
<Viewbox Stretch="Uniform">
<PathIcon Data="M44,12 C32,12 22,22 22,34 22,46 32,56 44,56 56,56 66,46 66,34 66,22 56,12 44,12z M44,0 C63,0 78,15 78,34 78,53 63,68 44,68 40,68 36.5,67.5 33,66 L32.5,66 14,90 0,79.5 18,55.5 17,55 C13,49 10,42 10,34 10,15 25,0 44,0z" />
</Viewbox>
</DataTemplate>
</Page.Resources>
<StackPanel Padding="40" HorizontalAlignment="Left">
<!-- Plain icon -->
<ContentPresenter
Width="40"
Height="40"
ContentTemplate="{StaticResource MagnifyingGlassPathIconCT}"
Foreground="Purple" />
<!-- Icon with a border -->
<Border
Width="40" Padding="7"
Height="40"
BorderBrush="Black"
BorderThickness="2">
<ContentPresenter ContentTemplate="{StaticResource MagnifyingGlassPathIconCT}" Foreground="Red" />
</Border>
<!-- Icon in a normal Button -->
<Button
Width="40"
Height="40"
ContentTemplate="{StaticResource MagnifyingGlassPathIconCT}"
Foreground="RoyalBlue" />
<!-- Icon in an AppBarButton -->
<AppBarButton
Width="40"
ContentTemplate="{StaticResource MagnifyingGlassPathIconCT}"
Foreground="Black"
Label="Search" />
</StackPanel>
For a solution that lets you define it in a Style, try writting an attached property like this:
public static string GetPathData(DependencyObject obj)
{
return (string)obj.GetValue(PathDataProperty);
}
public static void SetPathData(DependencyObject obj, string value)
{
obj.SetValue(PathDataProperty, value);
}
public static readonly DependencyProperty PathDataProperty =
DependencyProperty.RegisterAttached("PathData", typeof(string), typeof(ElementExtensions), new PropertyMetadata(null, (d, e) =>
{
if (d is Path path)
{
Binding b = new Binding { Source = e.NewValue };
path.SetBinding(Path.DataProperty, b);
}
}));
And now you can define a style like so:
<Style x:Key="BasePathStyle" TargetType="Path">
<Setter Property="e:ElementExtensions.PathData" Value="M 10,100 C 10,300 300,-200 300,100" />
</Style>
And then use it like so:
<Path Style="{StaticResource BasePathStyle}" />

Two UserControls, one binds, one doesn't

I have two very simple UserControls in a simple WinRT demo project. No Viewmodel--mostly just colored boxes for layout exploration. The first UserControl I created works fine. The second, very similar one, won't bind to any properties--it shows up as blank.
The first UserControl looks like this:
<UserControl
x:Class="Demo.SmallStartTile"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Demo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="SmallStartTileUC"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid x:Name="SmallTileGrid"
Margin="0,0,8,8"
Background="{Binding Background, ElementName=SmallStartTileUC}"
>
<Rectangle
Stroke="{Binding BorderBrush, ElementName=SmallStartTileUC}"
Grid.RowSpan="2"
/>
<TextBlock x:Name="SmallTileTitle"
Text="{Binding TileText, ElementName=SmallStartTileUC}"
Style="{StaticResource SmallTileHeader}"/>
<Path x:Name="IconPath"
Style="{StaticResource SmallTileIcon}"
Data="{Binding TileIconPathData, ElementName=SmallStartTileUC}" />
</Grid>
</UserControl>
namespace Demo
{
public sealed partial class SmallStartTile : UserControl
{
public static DependencyProperty TileTextProperty = DependencyProperty.Register("TileText", typeof(string), typeof(SmallStartTile), new PropertyMetadata("tile content"));
public string TileText
{
get { return (string)GetValue(TileTextProperty); }
set { SetValue(TileTextProperty, value); }
}
public static DependencyProperty TileIconPathDataProperty = DependencyProperty.Register("TileIconPathData", typeof(string), typeof(SmallStartTile), new PropertyMetadata("F0"));
public string TileIconPathData
{
get { return (string)GetValue(TileIconPathDataProperty); }
set { SetValue(TileIconPathDataProperty, value); }
}
public SmallStartTile()
{
this.InitializeComponent();
}
}
}
And the second one, which I made just like the first one by clicking Add New Item and picking UserControl in Blend:
<UserControl
x:Class="Demo.SmallMediaTile"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Demo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="SmallMediaTileUC"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid
Margin="0,0,8,8"
Background="{Binding Background, ElementName=SmallMediaTileUC}"
>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle
Stroke="{Binding BorderBrush, ElementName=SmallMediaTileUC}"
Grid.RowSpan="2"
/>
<Viewbox
Margin="30"
Child="{Binding Content, ElementName=SmallMediaTileUC}">
</Viewbox>
<TextBlock
Grid.Row="1"
Text="{Binding SourceText, ElementName=SmallMediaTileUC}"
Style="{StaticResource SmallMusicTileHeader}"/>
</Grid>
</UserControl>
namespace Demo
{
public sealed partial class SmallMediaTile : UserControl
{
public static DependencyProperty SourceTextProperty = DependencyProperty.Register("SourceText", typeof(string), typeof(SmallMediaTile), new PropertyMetadata("source"));
public string SourceText
{
get { return (string)GetValue(SourceTextProperty); }
set { SetValue(SourceTextProperty, value); }
}
public SmallMediaTile()
{
this.InitializeComponent();
}
}
}
I use the UserControl in the main Page like this:
<local:SmallMediaTile x:Name="Source1Tile"
Grid.Column="0" Grid.Row="0"
SourceText="Radio"
Background="Blue"
BorderBrush="Red">
<local:SmallMediaTile.Content>
<Canvas x:Name="radio_icon" Height="68" Width="34">
<Path Data="F1M299.6182,396.2988C299.6182,389.7208,297.0722,383.5548,292.4572,378.8398L288.6162,382.6758C292.2022,386.3718,294.1842,391.1778,294.1842,396.2988C294.1842,401.4238,292.2022,406.2368,288.6162,409.9328L292.4572,413.7738C297.0722,409.0538,299.6182,402.8808,299.6182,396.2988" Fill="White" Height="34.934" Canvas.Left="0" Stretch="Fill" Canvas.Top="16.501" Width="11.002"/>
<Path Data="F1M311.1738,396.2988C311.1738,386.6278,307.4348,377.5528,300.6788,370.6208L296.8258,374.4618C302.5658,380.3698,305.7398,388.0798,305.7398,396.2988C305.7398,404.5218,302.5658,412.2298,296.8258,418.1428L300.6788,421.9898C307.4348,415.0498,311.1738,405.9718,311.1738,396.2988" Fill="White" Height="51.369" Canvas.Left="8.21" Stretch="Fill" Canvas.Top="8.282" Width="14.348"/>
<Path Data="F1M322.7578,396.2988C322.7578,383.5298,317.8508,371.5348,308.9638,362.3388L305.1168,366.1748C312.9758,374.3538,317.3338,384.9778,317.3338,396.2988C317.3338,407.6208,312.9758,418.2488,305.1168,426.4268L308.9638,430.2748C317.8508,421.0698,322.7578,409.0798,322.7578,396.2988" Fill="White" Height="67.936" Canvas.Left="16.501" Stretch="Fill" Canvas.Top="0" Width="17.641"/>
</Canvas>
</local:SmallMediaTile.Content>
</local:SmallMediaTile>
The first one picks up all the properties and displays like I expect, but the second one only picks up the Viewbox content. I've looked through SO and been googling but can't figure out what the problem is. Sorry if this is longwinded.
Your root Grid is set as the Content of the UserControl while the Viewbox you have is trying to bind its Child property to the Content of the UserControl that is its ascendant. That is not allowed since the root Grid is already a parent of the UserControl and XAML controls can only exist in one place in the UI.
As noted, the problem is the UserControl's content can only be set once. When I bind the Viewbox Content to the UserControl content, the existing XAML is replaced. So, a blank box but for what I'm passing in. I guess to do what I had hoped, I'd need to make a custom control. Thanks for the help!