wpf xaml binding to translateTransform in Resource - wpf

I have a path defined as a resource in a resourceDictionary. In the code a rectangle with a transformgroup is defined. There are a few transforms to position the rectangle at a local datum then a translateTransform with a binding.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<GeometryGroup x:Key="box">
<RectangleGeometry Rect="0,0,70,25"/>
<GeometryGroup.Transform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<TranslateTransform X="-37.5" Y="-12.5"/>
<TranslateTransform X="500" Y="0"/>
<RotateTransform Angle="0"/>
<TranslateTransform X="{Binding BX}" Y="{Binding BY}"/>
</TransformGroup>
</GeometryGroup.Transform>
</GeometryGroup>
</ResourceDictionary>
The viewmodel holds an observable collection of "box" objects. Each box object has a BX and BY property to define the position of the box. The xaml page displays those boxes.
There is a data template as shown here which points to the resource:
<DataTemplate DataType="{x:Type cfg:Box}">
<Path Fill="White" Stroke="Black" Data="{StaticResource box}"/>
</DataTemplate>
Currently the boxes are displayed on a canvas with the following code:
<ItemsControl ItemsSource="{Binding Boxes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=BX}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=BY}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
This does locate the shapes on the canvas by setting the Canvas.Left and Canvas.Top properties. But instead what I would like to do is bind the BX and BY values from each "box" object in the observablecollection to the translateTransform associated with each item that is bound to the itemssource. (Right now the binding in the resource's translatetransform fails.)
So if I have 5 boxes in my observablecollection in the view model, and each one has a unique position in a BX and BY property (with an option to rotate, scale, etc) - how do I bind those properties to the corresponding items in the ItemsControl?

I don't follow why binding the contentpresenter canvas.left and top is not suitable.
Any bindings on properties in your geometrygroup cannot work as you have it in a resource dictionary.
Geometrygroup is a freezable and will be frozen if you put it in a resource dictionary. There is a built in mechanism calls Freeze() on any and all freezables you use in a resource dictionary. That means those variables aren't going to change.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.geometrygroup?view=windowsdesktop-7.0
Inheritance:
Object
DispatcherObject
DependencyObject
Freezable
Animatable
Geometry
GeometryGroup
You could put your geometrygroup in windows resources instead and it wouldn't be frozen.
The translatetransform has an Xproperty and Yproperty which I've animated in the past so I think the binding could work if there's a suitable BX and BY property in the datacontext of the resource.
You should be able to do something like:
<Style TargetType="ContentPresenter">
<Setter Property="RenderTransform">
<Setter.Value>
<TransformGroup>
<RotateTransform x:Name="RotateTransform"
Angle="{Binding Angle}"
CenterX="{Binding CenterX}"
CenterY="{Binding CenterY}"/>
<ScaleTransform x:Name="MirrorTransform"
ScaleX="{Binding MirroringX}"
ScaleY="{Binding MirroringY}"
CenterX="{Binding CenterX}"
CenterY="{Binding CenterY}"/>
<ScaleTransform x:Name="ScaleTransform"
ScaleX="{Binding ScaleX}"
ScaleY="{Binding ScaleY}"/>
</TransformGroup>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
So long as you don't get your transform frozen by putting it in a resource dictionary.

Related

Why my UserControl using a Path as ControlTemplate is not properly clickable?

I created a class derived from UserControl, directly on code (without XAML), and defined a ControlTemplate in a ResourceDictionary. This control template is a pair of ellipses, as follows:
<ControlTemplate x:Key="MusclePositionControlTemplate" TargetType="{x:Type local:MusclePositionControl}">
<ControlTemplate.Resources>
<EllipseGeometry x:Key="bolinha" RadiusX="{StaticResource radius}" RadiusY="{StaticResource radius}">
<EllipseGeometry.Center>
<Point X="0" Y="{StaticResource distance}"/>
</EllipseGeometry.Center>
</EllipseGeometry>
</ControlTemplate.Resources>
<Path Fill="#9900ffff" StrokeThickness="1" Stroke="Black">
<Path.Data>
<GeometryGroup>
<StaticResource ResourceKey="bolinha"/>
<GeometryGroup>
<GeometryGroup.Transform>
<ScaleTransform ScaleY="-1"/>
</GeometryGroup.Transform>
<StaticResource ResourceKey="bolinha"/>
</GeometryGroup>
</GeometryGroup>
</Path.Data>
<Path.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding Angle, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
<TranslateTransform
X="{Binding X, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Y="{Binding Y, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</TransformGroup>
</Path.RenderTransform>
</Path>
</ControlTemplate>
When I edit this template in Blend, I can select the path clicking on it, and it displays its outlines like this:
But when I add the actual control to a canvas, with its X, Y and Angle properties set to some value, the control renders itself fine, but its defining geometry keeps being that of a rectangle at the origin, and this affects hit testing (I cannot select at design time, or click at runtime, by clicking at the shape itself):
So my questions are:
How should I create a ControlTemplate using a Path as "root" visual element, so that the clickable area of the control is that Path?
Assuming a Path should not or could not be used as root visual for a ControlTemplate, how should I achieve the same end result?

How do I cut transparent bits out of an otherwise opaque canvas in WPF?

I have a drawing in WPF, where I set up a canvas, with a background colour and then draw lots of things over that. Now I want to make cuttouts, i.e. have transparent regions in fairly arbitrary shapes. My approach is to use a VisualBrush as an opacity mask.
Here is a simplified version of the work in progress
<Canvas Background="LightGray" Width="{Binding SizeX}" Height="{Binding SizeY}">
<Canvas.OpacityMask>
<VisualBrush
VIEWPORT / TILING VOODO HERE
>
<VisualBrush.Visual>
<Canvas Background="#ffffffff" Width="{Binding SizeX}" Height="{Binding SizeY}">
DRAW CUTTOUTS HERE.
If I got the Viewport/tiling voodoo right I should
just use the same coordinate system as for visible things.
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
</Canvas.OpacityMask>
DRAW VISIBLE THINGS HERE
</Canvas>
But I am running afoul of retained-mode drawing. The idea was to fill the background alpha channel with #ff so that things are opaque by default, and then have transparent cuttouts. But of course what that evaluates to is transparent shapes on top of a completely opaque background, and thus no cuttouts.
So how do I get my cuttouts?
here you go
I placed a inner canvas to host your content and used the same canvas as the visual for visual brush which will be used for the opacity mask for outer canvas. this will enable you to cut the outer canvas based on the content in inner canvas
<Canvas x:Name="outer" Background="Green" Width="100" Height="100">
<Canvas x:Name="inner" Width="{Binding ActualWidth,ElementName=outer}" Height="{Binding ActualHeight,ElementName=outer}">
<!--your arbitery content here-->
<Ellipse Width="20" Height="10" Fill="Gray" Canvas.Left="10" Canvas.Top="10"/>
</Canvas>
<Canvas.OpacityMask>
<VisualBrush Visual="{Binding ElementName=inner}"
ViewportUnits="Absolute" ViewboxUnits="Absolute"
Viewbox="0,0,100,100" Viewport="0,0,100,100"/>
<!--you may need to write conveter class for conversion from SizeX & SizeY to Viewbox & Viewport-->
</Canvas.OpacityMask>
</Canvas>
all what is hard-coded in the example is the Viewbox & Viewport numbers (also hard-coded the width and height of outer canvas to match the numbers), you may write a converter for the same to bind with SizeX & SizeY as all the properties in visual brush need to be accurate in order to map it correctly
note do not apply background to inner canvas
Update
as discussed if you want to cutout the background of the canvas instead of showing the shapes here is the sample, also added trigger to disable the opacity mask if there are no shapes to cut out
<Canvas Background="Green">
<Canvas.Style>
<Style TargetType="Canvas">
<Style.Resources>
<Canvas x:Key="maskVisual">
<!--your arbitery content here-->
<Ellipse Width="20" Height="10" Fill="Gray"
Canvas.Left="10" Canvas.Top="10"/>
</Canvas>
</Style.Resources>
<Setter Property="OpacityMask">
<Setter.Value>
<VisualBrush Visual="{StaticResource maskVisual}"
ViewportUnits="Absolute" ViewboxUnits="Absolute"
Viewbox="0,0,100,100" Viewport="0,0,100,100"/>
<!--you may need to write conveter class for conversion from SizeX & SizeY to Viewbox & Viewport-->
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Children.Count, Source={StaticResource maskVisual}}" Value="0">
<Setter Property="OpacityMask" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Style>
</Canvas>
1 point to notice is that, since canvas do not clip by default you may not require to set the width and height explicitly

WPF change Fill Color Brush

In a Resource Dictionary I have stored a ViewBox with a Canvas
<Style x:Key="MyPathStyle" TargetType="Path">
<Setter Property="Fill" Value="{Binding BackgroundColorBrush,
RelativeSource={RelativeSource AncestorType=iconcontrols:IconAsVisualBrush}}"/>
</Style>
<Viewbox x:Key="Icon2">
<Canvas Width="40.000" Height="40.000">
<Canvas>
<Path Fill="#ff99baf4" Data="F1 M 14.377,23.798" />
<Path Style="{StaticResource MyPathStyle}" Data="..." />
</Canvas>
</Canvas>
</Viewbox>
So I want to change the color of the second Path using the BackgroundColorBrush of my control Container (called IconAsVisualBrush ).
It is
<Grid x:Name="GridIconBrush" Width="40" Height="40">
<Grid.Background>
<VisualBrush x:Name="CtrlVisualBrush" Stretch="Uniform" />
</Grid.Background>
</Grid>
The VisualBrush is set in cs:
private static void OnIconBrushResourceChanged(DependencyObject source
, DependencyPropertyChangedEventArgs e)
{
IconAsVisualBrush control = source as IconAsVisualBrush;
control.CtrlVisualBrush.Visual = (Viewbox)Application.Current.FindResource(e.NewValue);
}
In my UserControl I can draw the ViewBox with the folliwing xaml:
<iconcontrols:IconAsVisualBrush BackgroundColorBrush="White"
IconBrushResource="Icon2"/>
<iconcontrols:IconAsVisualBrush BackgroundColorBrush="Red"
IconBrushResource="Icon2"/>
The canvas is correctly drawn but not the color. I receive:
Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='IconAsVisualBrush', AncestorLevel='1''. BindingExpression:Path=BackgroundColorBrush; DataItem=null; target element is 'Path' (Name=''); target property is 'Fill' (type 'Brush')
Is there a way to change only the Path Fill color set inside a owner Control (not a dynamic resource that make all IconAsVisualBrush with the same color) so that I can draw the same shape with different fill colors?
Your issue is that the Setter in your Style is unable to find the IconAsVisualBrush, presumably because it is not part of the Path's visual tree. Have you considered using triggers? It's difficult to suggest a solution without knowing your application architecture and what is calling OnIconBrushResourceChanged - however since we're talking about WPF, I'll make an educated guess that you are using MVVM. If so, you could use DataTriggers like so:
<Style x:Key="MyPathStyle" TargetType="Path">
<Setter Property="Fill" Value="White" />
<Style.Triggers>
<DataTrigger Binding="{MyColourChangedProperty}"
Value="True">
<DataTrigger.Setters>
<Setter Property="Fill" Value="Red"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
EDIT: To clarify, the style I've suggested here will give you a default fill of white, but if you set 'MyColourChangedProperty' (or whatever you bind to) to true, it will turn red.

How to get updated automatically WPF TreeViewItems with values based on .Net class properties?

Good morning.
I have a class with data derived from InotifyPropertyChange. The data come from a background thread, which searches for files with certain extension in certain locations. Public property of the class reacts to an event OnPropertyChange by updating data in a separate thread. Besides, there are described in XAML TreeView, based on HierarhicalDataTemplates. Each TextBlock inside templates supplied ItemsSource = "{Binding FoundFilePaths, Mode = OneWay, NotifyOnTargetUpdated = True}".
<TreeView x:Name="FoundFiles_TreeView" Opacity="15" Background="White" BorderThickness="5" FontFamily="Arial" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Top" Height="360" FontWeight="Bold" Foreground="#FF539DBE" TargetUpdated="FoundFiles_TreeView_TargetUpdated">
<TreeView.ItemContainerStyle >
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="TreeViewItem.Tag" Value="InfoNode" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Foreground" Value="Brown"/>
<Style.Triggers>
<Trigger Property="IsMouseCaptured" Value="True">
<Setter Property="IsSelected" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType = "{x:Type lightvedo:FilesInfoStore}" ItemsSource="{Binding FoundFilePaths, Mode=OneWay, NotifyOnTargetUpdated=True}">
<!--Здесь указываются узлы дерева-->
<StackPanel x:Name ="TreeNodeStackPanel" Orientation="Horizontal">
<TextBlock Margin="5,5,5,5" TargetUpdated="TextBlockExtensions_TargetUpdated">
<TextBlock.Text>
<MultiBinding StringFormat="Files with Extension {0}">
<Binding Path="FileExtension"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Button x:Name="OpenFolderForThisFiles" Click="OnOpenFolderForThisFiles_Click" Margin="5, 3, 5, 3" Width="22" Background="Transparent" BorderBrush="Transparent" BorderThickness="0.5">
<Image Source="images\Folder.png" Height="16" Width="20" >
</Image>
</Button>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type lightvedo:FilePathsStore}">
<TextBlock Text="{Binding FilePaths, Mode=OneWay, NotifyOnTargetUpdated=True}" TargetUpdated="OnTreeViewNodeChildren_Update" />
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform AngleX="-0.083"/>
<RotateTransform/>
<TranslateTransform X="-0.249"/>
</TransformGroup>
</TreeView.RenderTransform>
<TreeView.BorderBrush>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="#FF74591F" Offset="0" />
<GradientStop Color="#FF9F7721" Offset="1" />
<GradientStop Color="#FFD9B972" Offset="0.49" />
</LinearGradientBrush>
</TreeView.BorderBrush>
</TreeView>
Q: Why is the data from a class derived from INotifyPropertyChange does not affect the display of tree items. Do I understand: The interface will make INotifyPropertyChange be automatically redrawn TreeViewItems or do I need to manually carry out this operation? Currently TreeViewItems not updated and PropertyChamged always null. A feeling that no subscribers to the event OnPropertyChanged.
You don't need to set the NotifyOnTargetUpdated.
Instead, make sure to raise the PropertyChanged event (with the appropriate property-name passed with the PropertyChangedEventArgs passed to the handler) on the parent entity each time the paths collection is updated, or have the navigation property be an implementation of INotifyCollectionChanged.
I think I found the reason.
My background thread that constantly scans the folder creates a new instance of the data class derived from INotifyPropertyChanged, which serves as a source for TreeViewItems (ItemsSource). This principle is chosen because it is impossible to predict what should be done with a collection of files found: add a new item, remove an existing or edit an existing. If I ever intended to substitute ItemsSource trick with PropertyChange does not work. So for me the only solution was to call from the separate (background, scanning folders) thread Refresh() method for TreeViewItems.
public delegate void RefreshTreeViewItemsDelegate();
Dispatcher.FromThread(_guiThread).BeginInvoke(DispatcherPriority.Render, new RefreshTreeViewItemsDelegate (RefreshTreeItems))
// Some code ommited
private void RefreshTreeItems()
{
_popupWindow.FoundFiles_TreeView.ItemsSource = _treeViewNodesItems;
_popupWindow.FoundFiles_TreeView.Items.Refresh();
}
Use of this situation with these classes inherited from INotifyPropertyChanged useless. ItemsControl, you bind to this class expects only add, delete or change items, but not replace ItemsSource a new instance of the data class.

WPF Bind to parent property from within nested element using style

I've been trying to build a text box with a hint that's displaying while it's empty.
I'm having trouble setting the hint text from within a style.
To be precise, this works (that is, it binds correctly):
<TextBox Tag="hint text">
<TextBox.Background>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=TextBox}}" FontStyle="Italic" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</TextBox.Background>
</TextBox>
but, when I move it to the Style, it doesn't:
<Style TargetType="TextBox" x:Key="stlHintbox">
<Style.Triggers>
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" Value="">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Tag="inner" Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=TextBox}}"
FontStyle="Italic" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<TextBox Tag="hint text" Style="{StaticResource stlHintbox}" />
So what's the catch? How can I bind to ancestor property from within a style?
The problem is not with the RelativeSource but with the way you are using the VisualBrush. Recall that Styles are shared between the elements you apply them to. The reason that your example doesn't work is that, in effect you are trying to share a single textbox (the one you tagged "inner") with multiple parent textboxes.
To see why this is a problem, try a thought experiment: The inner textbox gets created once (roughly speaking, this will happen when the style is created). Which of the textboxes that the style gets applied to should be chosen as the ancestor of the inner text box when you use the RelativeSource binding?
This is why DataTemplates and ControlTemplates exist in WPF. Rather than actually instantiate visuals directly, they define a template that allow multiple copies of visuals to be created as needed.
Reativesource doesn't work as expected.
It is better to create watermark textbox using control template. But your version could work:
<Window.Resources>
<Style TargetType="TextBox" x:Key="stlHintbox">
<Style.Triggers>
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" Value="">
<Setter Property="TextBox.Background">
<Setter.Value>
<VisualBrush Stretch="None" Visual="{Binding ElementName=hintText}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox Tag="hint text" x:Name="myTextBox" Style="{StaticResource stlHintbox}" />
<Border Visibility="Hidden">
<TextBlock x:Name="hintText" Text="{Binding Tag, ElementName=myTextBox}" FontStyle="Italic" Foreground="LightGray" />
</Border>
</StackPanel>

Resources