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

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}" />

Related

MasterDetail ListView and editable ContentPresenter: what is wrong?

I'm based on the official Microsoft sample to create a MasterDetail ListView:
MasterDetail ListView UWP sample
I have adapted it to my case, as I want that users can edit directly selected items from the ListView. But I meet a strange comportement:
when I add a new item to the ListView, the changes of the current item, done in the details container, are well saved
but when I select an existing item in the ListView, the changes of the current item, done in the details container, are not saved
Here is a screenshot of my app:
The XAML of my ListView is like this:
<!-- Master : List of Feedbacks -->
<ListView
x:Name="MasterListViewFeedbacks"
Grid.Row="1"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource MasterListViewFeedbacksItemTemplate}"
IsItemClickEnabled="True"
ItemsSource="{Binding CarForm.feedback_comments}"
SelectedItem="{Binding SelectedFeedback, Mode=TwoWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.FooterTemplate>
<DataTemplate>
<CommandBar Background="White">
<CommandBar.Content>
<StackPanel Orientation="Horizontal">
<AppBarButton Icon="Add" Label="Add Feedback"
Command="{Binding AddItemFeedbacksCommand}" />
<AppBarButton Icon="Delete" Label="Delete Feedback"
Command="{Binding RemoveItemFeedbacksCommand}" />
</StackPanel>
</CommandBar.Content>
</CommandBar>
</DataTemplate>
</ListView.FooterTemplate>
</ListView>
The XAML of the ListView's ItemTemplate is:
<DataTemplate x:Key="MasterListViewFeedbacksItemTemplate" x:DataType="models:Feedback_Comments">
<StackPanel Margin="0,11,0,13"
Orientation="Horizontal">
<TextBlock Text="{x:Bind creator }"
Style="{ThemeResource BaseTextBlockStyle}" />
<TextBlock Text=" - " />
<TextBlock Text="{x:Bind comment_date }"
Margin="12,1,0,0" />
</StackPanel>
</DataTemplate>
The XAML of the Details container is like this:
<!-- Detail : Selected Feedback -->
<ContentPresenter
x:Name="DetailFeedbackContentPresenter"
Grid.Column="1"
Grid.RowSpan="2"
BorderThickness="1,0,0,0"
Padding="24,0"
BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
Content="{x:Bind MasterListViewFeedbacks.SelectedItem, Mode=OneWay}">
<ContentPresenter.ContentTemplate>
<DataTemplate x:DataType="models:Feedback_Comments">
<StackPanel Visibility="{Binding FeedbacksCnt, Converter={StaticResource CountToVisibilityConverter}}">
<TextBox Text="{Binding creator, Mode=TwoWay}" />
<DatePicker Date="{Binding comment_date, Converter={StaticResource DateTimeToDateTimeOffsetConverter}, Mode=TwoWay}"/>
<TextBox TextWrapping="Wrap" AcceptsReturn="True" IsSpellCheckEnabled="True"
Text="{Binding comment, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ContentPresenter.ContentTemplate>
<ContentPresenter.ContentTransitions>
<!-- Empty by default. See MasterListView_ItemClick -->
<TransitionCollection />
</ContentPresenter.ContentTransitions>
</ContentPresenter>
The "CarForm" is the main object of my ViewModel. Each CarForm contains a List of "Feedback_Comments".
So in my ViewModel, I do this when I add a new comment:
private void AddItemFeedbacks()
{
FeedbacksCnt++;
CarForm.feedback_comments.Add(new Feedback_Comments()
{
sequence = FeedbacksCnt,
creator_id = user_id,
_creator = username,
comment_date = DateTime.Now
});
SelectedFeedback = CarForm.feedback_comments[CarForm.feedback_comments.Count - 1];
}
=> the changes done in the Feedback_Comment that was edited before the add are well preserved
I don't do anything when the user select an existing Feedback_Comment: this is managed by the XAML directly.
=> the changes done in the Feedback_Comment that was edited before to select anoter one are not preserved
=> Would you have any explanation?
The TwoWay binding for the Text property is updated only when the TextBox loses focus. However, when you select a different item in the list, the contents of the TextBox are no longer bound to the original item and so are not updated.
To trigger the update each time the Text contents change, so that the changes are reflected immediately, set the UpdateSourceTrigger set to PropertyChanged:
<TextBox Text="{Binding comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Triggering changes everywhere
To ensure your changes are relflected everywhere including the list, you will need to do two things.
First, your feedback_comments is of type ObservableCollection<Feedback_Comments>. This ensures that the added and removed items are added and removed from the ListView.
Second, the Feedback_Comments class must implement the INotifyPropertyChanged interface. This interface is required to let the user interface know about changes in the data-bound object properties.
Implementing this interface is fairly straightforward and is described for example on MSDN.
The quick solution looks like this:
public class Feedback_Comments : INotifyPropertyChanged
{
// your code
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged( [ CallerMemberName ]string propertyName = "" )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
Now from each of your property setters call OnPropertyChanged(); after setting the value:
private string _comment = "";
public string Comment
{
get
{
return _comment;
}
set
{
_comment = value;
OnPropertyChanged();
}
}
Note, that the [CallerMemberName] attribute tells the compiler to replace the parameter by the name of the caller - in this case the name of the property, which is exactly what you need.
Also note, that you can't use simple auto-properties in this case (because you need to call the OnPropertyChanged method.
Bonus
Finally as a small recommendation, I see you are using C++-like naming conventions, which does not fit too well into the C# world. Take a look at the recommended C# naming conventions to improve the code readability :-) .

how to use Multibinding.StringFormat on Fill property of a rectangle?

I've created an ItemControl with a dataTemplate that contains a rectangle that will be colorized based of the ItemsSource. The date being fed to my application is a color hex code that does not contain the hash sign (#). Just a 6-character string. To get the color to show up correctly i need to format the 6-character string with the # in front of it. exp #A31F34
Here's the XAML
<DataTemplate x:Key="ColorSequenceSwatchPreviews">
<Rectangle Name="ColorSwatch" Height="20" Width="120" RadiusX="3" RadiusY="3" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,3,0,3">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Fill">
<Setter.Value>
<MultiBinding>
<MultiBinding.StringFormat><![CDATA[#{0}]]></MultiBinding.StringFormat>
<Binding Path="InnerXml" Mode="OneWay" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Rectangle.Style>
</Rectangle>
I'm using a MultiBinding.StringFormat to format the string into a Hexcode properly, but am stumped as to why the fill of the rectangle is not colorizing.
I am able to get the rectangle to colorize if I do the MultiBinding with a TextBox, then bind the rectangle's fill property to the Text property of the textBox. However, I would much prefer binding directly from the rectangle's fill property like in my first example since it is cleaner.
<DataTemplate x:Key="ColorSequenceSwatchPreviews">
<StackPanel Orientation="Horizontal" Margin="0,3,0,3" VerticalAlignment="Center" HorizontalAlignment="Left">
<TextBox x:Name="Hexcode" Visibility="Collapsed">
<TextBox.Text>
<MultiBinding>
<MultiBinding.StringFormat><![CDATA[#{0}]]></MultiBinding.StringFormat>
<Binding Path="InnerXml" Mode="OneWay" />
</MultiBinding>
</TextBox.Text>
</TextBox>
<Rectangle Name="ColorSwatch" Height="20" Width="120" RadiusX="3" RadiusY="3" VerticalAlignment="Center" HorizontalAlignment="Left">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="{Binding ElementName=Hexcode,Path=,Mode=OneWay}" />
</Style>
</Rectangle.Style>
</Rectangle>
</StackPanel>
Is there a way to get the first example to work or am i stuck with using the code from the second example?
This can be achieved far more easily using a Converter. You don't even need MultiBinding for it. Simple Binding with a Converter should do it:
Here is the converter:
<ValueConversion(GetType(String), GetType(SolidColorBrush))>
Public Class HexToBrushConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
Return DirectCast(New BrushConverter().ConvertFrom("#" & value.ToString()), SolidColorBrush)
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Return Nothing
End Function
End Class
All you need to do now is to create an object of the converter in your Resources section:
<local:HexToBrushConverter x:Key="HexToBrushConverter" />
(local is the namespace of your project where you define this converter class)
and then use it in the Fill property:
<Rectangle Fill="{Binding ElementName=Hexcode, Path=, Mode=OneWay, Converter={StaticResource HexToBrushConverter}}" />

XAML images as StaticResources throw an error in Windows Universal app

I have some vector icons I want to use throughout my windows universal application. I've added them to a resource dictionary, added the dictionary to a merged dictionary in app.xml, and tried using the resources in a page. The images actually show up in the editor preview window, but throw an error when I attempt to debug the application.
ManaSymbols.xaml
<?xml version="1.0" encoding="UTF-8"?>
<xaml:ResourceDictionary
xmlns:xaml="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Viewbox x:Key="BlueManaSymbol" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Stretch="Uniform">
<Canvas Width="100" Height="100">
<Canvas.RenderTransform>
<TranslateTransform X="0" Y="0"/>
</Canvas.RenderTransform>
<Canvas>
<Canvas>
<Ellipse Width="100" Height="100" Fill="#FFC1D7E9"/>
</Canvas>
<Path Fill="#FF0D0F0F">
<Path.Data>
M 68.91657 83.71021 C 58.968869 94.408441 39.101769 93.367284 30.985174 80.955583 23.700186 70.338629 25.060135 55.965661 30.782622 44.970201 37.43962 31.696018 47.119757 19.99635 58.53257 10.53421 c -1.779599 7.330526 -2.971221 15.369494 0.5678 22.406733 4.282692 9.098857 12.226705 16.065144 15.7407 25.557642 2.950311 8.612064 0.582748 18.823437 -5.9245 25.211625 z m -0.129 -27.362 c -0.82319 -2.47714 -5.460323 -8.506164 -4.125006 -2.813916 -0.362035 4.191263 -1.937779 8.124558 -3.178994 12.106916 -0.269255 7.254198 11.007675 4.685165 9.226 -1.795 -0.01849 -2.611632 -0.877381 -5.133602 -1.922 -7.498 z
</Path.Data>
</Path>
</Canvas>
</Canvas>
</Viewbox>
</xaml:ResourceDictionary>
App.xaml
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Assets/ManaSymbols.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Page.xaml
<ContentControl Content="{StaticResource BlueManaSymbol}" Grid.Row="0" Margin="70,0,0,0" Width="30" Height="30" />
Error throw when debugging:
A first chance exception of type 'Windows.UI.Xaml.Markup.XamlParseException' occurred in MagicStats.Windows.exe
WinRT information: Failed to assign to property 'Windows.UI.Xaml.Controls.ContentControl.Content'. [Line: 79 Position: 25]
The error message seems to indicate that the binding failed, but it actually works in the design window. Any thoughts are appreciated.

Get selected controls from Pivot.ItemTemplate

I have a XAML:
<phone:Pivot Name="pivot" SelectionChanged="pivot_SelectionChanged">
<phone:Pivot.ItemTemplate>
<DataTemplate>
<ViewportControl Name="viewport">
<Canvas Name="canvas">
<Image Name="image"
RenderTransformOrigin="0,0"
CacheMode="BitmapCache"
Source="{Binding ImageSource}">
<Image.RenderTransform>
<ScaleTransform x:Name="xform"/>
</Image.RenderTransform>
</Image>
</Canvas>
</ViewportControl>
</DataTemplate>
</phone:Pivot.ItemTemplate>
</phone:Pivot>
Can I get current ViewportControl, Canvas, etc for selected item? For example
private void pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//get ViewportControl, Canvas, etc
}
Not necessarily with SelectionChanged, may have other solutions?
Yes, but you have to search for them. You need to use VisualTreeHelper and hunt for children of the desired type.
Or you can have some mapping in the code-behind for all controls and the parent pivot items.

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

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>