How can I programmatically access elements defined in a ContentTemplate? - c#

Let's say I've created a UserControl with the following ContentTemplate defined in XAML:
<UserControl.ContentTemplate>
<DataTemplate>
<Ellipse Name="myEllipse" Stroke="White"/>
<ContentPresenter Content="{TemplateBinding Content}"/>
</DataTemplate>
</UserControl.ContentTemplate>
How would I access the "myEllipse" element within my code so that, for example, I could find its height with "myEllipse.Height"? I cannot access it by name directly. I attempted to create a reference to it with:
Ellipse ellipse = ContentTemplate.FindName("myEllipse",this) as Ellipse;
It crashes when I run the program, saying it can't create an instance of my class. Perhaps I'm not using FindName correctly. If anyone can help me out it would be much appreciated.
Thanks,
Dalal

In order to use FindName on a DataTemplate, you will need a reference to the ContentPresenter. See Josh Smith's article How to use FindName with a ContentControl.
What you may actually want to do is to use a ControlTemplate rather than a DataTemplate. This should be easier to use and will let users of your control apply their own content templates or use implicit templates. If you do something like this:
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<ContentPresenter/>
<Ellipse Name="myEllipse" Stroke="White"/>
</Grid>
</ControlTemplate>
</UserControl.Template>
Then in code (perhaps in an OnApplyTemplate override) you will be able to do this:
var ellipse = Template.FindName("myEllipse", this) as Ellipse;
You should also decorate your class with a TemplatePartAttribute like this:
[TemplatePart(Name="myEllipse", Type = typeof(Ellipse))]
So that if anyone re-templates your control they know to provide an Ellipse element with that name. (This is less important if the class is only used internally.)
Finally, if all you want to do is change the color of the Ellipse, then you may just want to use data binding. You could create an EllipseColor dependency property on your control and just set Stroke="{TemplateBinding EllipseColor}".

Try
<Ellipse Name="myEllipse" Stroke="{TemplateBinding Background}"/>
instead of programmatically changing it.
There's a similar example here, with a blue filled ellipse.
http://msdn.microsoft.com/en-us/library/system.windows.controls.contentpresenter.aspx

Related

How to change color of all shapes in window with C# and XAML?

Brief
I am trying to programmatically change the colour of specific elements at runtime. The project currently uses Telerik and I am able to change the theme at runtime: This works as expected with no issues. I can't, however, figure out how to change the fill or stroke colour at runtime of custom shape elements in XAML.
Within my project I have a ResourceDictionary file named _Icons.xaml that contains vector shapes to use as the content for other controls (such as buttons).
Code
App.xaml.cs
I am using the following code to change the theme's marker colours at runtime.
GreenPalette.Palette.MarkerColor = (Color)ColorConverter.ConvertFromString("#FF000000");
_Icons.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyNamespace">
<ControlTemplate x:Key="Box">
<Viewbox>
<Rectangle Width="357" Height="357" Fill="#000000"/>
</Viewbox>
</ControlTemplate>
<ControlTemplate x:Key="BoxOutline">
<Viewbox>
<Rectangle Width="357" Height="357" StrokeThickness="45" Stroke="#000000"/>
</Viewbox>
</ControlTemplate>
</ResourceDictionary>
MainWindow.xaml
<telerik:RadButton>
<StackPanel>
<ContentControl Template="{StaticResource Box}" Height="58"/>
<TextBlock HorizontalAlignment="Center" Margin="0,5,0,0">Box</TextBlock>
</StackPanel>
</telerik:RadButton>
<telerik:RadButton>
<StackPanel>
<ContentControl Template="{StaticResource BoxOutline}" Height="58"/>
<TextBlock HorizontalAlignment="Center" Margin="0,5,0,0">BoxOutline</TextBlock>
</StackPanel>
</telerik:RadButton>
Question
In _Icons.xaml I have the following lines:
<Rectangle Width="357" Height="357" Fill="#000000"/>
<Rectangle Width="357" Height="357" StrokeThickness="45" Stroke="#000000"/>
Given the following line in App.xaml.cs:
GreenPalette.Palette.MarkerColor = (Color)ColorConverter.ConvertFromString("#FF000000");
How can I either...
Programmatically change the values of Fill and/or Stroke (an element that only has Fill set should only change the Fill value and not add a Stroke attribute) from the App.xaml.cs file? Or ...
Bind the values in XAML for Fill or Stroke to receive the value given by my App.xaml.cs file?
Thank you for taking the time to read my question. Any help regarding this is greatly appreciated.
First i advise you to eject that controls off your resource sheet so you can actually control them properly.
When you do that, go the code behind your control and just use dependency property of type 'Color' of the 'SolidColorBrush' that is used by the background and then bind it by element name, you gotta build the project at least once before attempting to bind.
Here is how you write a dependency property
hint: in VS write 'propdp' and hit tab twice to bring up a template, but you can use mine for now.
public Color _color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
public static readonly DependencyProperty ColorProperty =
DependencyProperty.Register("_color", typeof(Color), typeof(Fileentity), null);
after you build once go to the xalm and put this inside your rectangle:
<Grid.Background>
<SolidColorBrush Color="{Binding
_color,ElementName=YourControlName" />
</Grid.Background>
if you do it right you will be able to access this property when inserting the control on you Page like
<local:YourcontrolName _color="{x:Bind MyColorProperty }"/>
where 'MyColorProperty' is a property that implements INotifyPropertyChanged.
An alternative way is to use a datacontext directly on the usercontrol and just bind your color to one of its properties like:
public YourControl(){
this.InitializeComponent();
this.DataContext = new MyClassDataContext();
var myContext= (MyClassDataContext)this.DataContext;
_color=MyContext.MyColorProperty;}
Where MyClassDataContext is any given class that contains a Color property(MyColorProperty) of your choosing.
You need a Dependency property here as well that binds to your Controls xalm like i showed before.
I know all this is might too hard to grasp at once, thats cause it requires basic knowledge of MvvM.

XAML Listbox Style Override Control Template for Order Controls

Most of the tutorials and questions I see are about restyling the listbox to look different, but I'm interested in adding additional controls to make it behave differently. I initially started out trying to make the list builder control out of a checkbox list, but found myself too deep. I decided to abstract and start with a smaller problem.
What I am looking to do first, to get a better understanding of how this works is add "up" and "down" buttons next to the control. I think this can all be done in xaml, so to try and pressure myself to stick to that I'm working in Kaxaml.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<!-- ListBox Order Button Style
Col 1
Listbox
Col 2
Buttons Up and Down
-->
<Style x:Key="{x:Type ListBox}" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Rectangle Fill="Yellow"/>
<!--<ListBox></ListBox>-->
</Grid>
<StackPanel Grid.Column="1">
<Button>Up</Button>
<Button>Down</Button>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid>
<ListBox>
<TextBlock>Value 1</TextBlock>
<TextBlock>Value 2</TextBlock>
<TextBlock>Value 3</TextBlock>
<TextBlock>Value 4</TextBlock>
</ListBox>
</Grid>
</Page>
I am currently hung up on a few things.
1) When I try to use a ListBox where the Yellow Rectangle is I start getting infinite loop problems.
2) I'm not sure how to connect the buttons to the listbox once it is there. I think Triggers is the answer, but I don't have much experience with them.
Your infinite loop can be addressed by not relying on the TargetType to apply the style. Instead, apply the style explicitly via a named key (i.e. something other than {x:Type Listbox}). That way the style is applied only when you specifically want it to be applied.
"Connecting" the buttons can be done a variety of ways. The simplest would be to handle the Button.Click event and perform whatever action you want there.
All that said, I think you're going about this the wrong way. Let a ListBox be a ListBox; don't try to make it into something it's not. If you want a reusable control that adds functionality around a ListBox, like buttons to control the contents of the ListBox, you should probably be authoring a UserControl, which is essentially a composite control made up of whatever you want.
Doing so will give you a lot more control over the appearance of the control. You'll also have the opportunity to declare dependency properties on your control that are specific to exactly what that control needs to support (something you can't do just with a Style). Yes, it also means you'll have to expose properties of contained elements via new properties in your UserControl that effectively delegate to the contained elements, but that's a small price to pay for the flexibility and relative simplicity of creating the UserControl in the first place.

Is it possible to bind to a property on the container of the Adorned Element?

In my WPF app we are using an adorner for displaying validation messages, in the particular case there is a single row grid that has multiple controls some of which have validation. The problem I'm having is that I want to force the width of the error message control to be the same as the grid but can't seem to find a way to reference that grid from the adorner template. Here is a sample of what I tried:
<ControlTemplate x:Key="Local_TopAdornedTemplateWide">
<StackPanel>
<AdornedElementPlaceholder x:Name="adornedElement"/>
<TextBlock MaxWidth="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType=Grid}, ElementName=adornedElement}"
TextWrapping="Wrap"
Text="{Binding Converter={StaticResource Local_ValidationErrorMessageConverter}}"
Style="{DynamicResource Error_Text}"
Padding="2 1 0 0"
Visibility="{Binding ElementName=adornedElement, Mode=OneWay, Path=AdornedElement.IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</StackPanel>
</ControlTemplate>
This causes the application to crash with an XamlParseException.
Ideally the solution would not be specific to a grid so that it would get the width of any container type, but for now grid is the only use case.
Edit:
Here is an example of another template we use in the application; this template would not work for my case as it would limit the error to be the width of a single column of the aforementioned grid:
<ControlTemplate x:Key="Local_TopAdornedErrorTemplate">
<StackPanel>
<AdornedElementPlaceholder x:Name="adornedElement"/>
<TextBlock MaxWidth="{Binding ElementName=adornedElement, Path=ActualWidth}"
TextWrapping="Wrap"
Text="{Binding Converter={StaticResource Local_ValidationErrorMessageConverter}}"
Style="{DynamicResource Error_Text}"
Padding="2 1 0 0"
Visibility="{Binding ElementName=adornedElement, Mode=OneWay, Path=AdornedElement.IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</StackPanel>
</ControlTemplate>
Using snoop I captured the following two screenshots (I could not take one of the full stack to prevent posting anything proprietary)
This shot shows the grid I mentioned previously, within this it is the FinancialTextBox item that is being adorned
This shot shows two things, the item selected in blue is the highest ancestor of the grid in the previous shot, the yellow highlight is the Textbox from the content template
With those two it seems to be apparent that (based on information from Contango's answer) the two items aren't not in the same visual tree which would lead me to believe my question is not possible. However the second template I added (which does work) points that at least some visual information from the adorned element lives on in the place holder.
So now my question boils down to a) does this information include the parent of the adorned element and b) how can this be accessed via a binding on a different element?
This ended up being a lot simpler than the path I was trying to go down.
I was doing some reading on the AdornedElementPlaceholder class and came across this entry on MSDN and noticed that the class actually has a property called parent, with that I tried the following binding and it works perfectly:
MaxWidth="{Binding ElementName=adornedElement,
Mode=OneWay,
Path=AdornedElement.Parent.ActualWidth}"
WPF is quite powerful and flexible.
You can bind any property in any XAML tag to any property in any other XAML tag.
For example, you could write a test app that binds the Text property of an input box to the Text property of a label, so as you type something into the text box, the label would change automatically (assuming you use UpdateSourceTrigger=PropertyChanged). This is a direct XAML to XAML binding, with no C# in sight.
Similarly, you could bind the width of your error box to the width of the parent control, whatever that may be.
Google RelativeSource and AncestorType, this is a great link:
http://druss.co/2013/10/wpf-binding-examples/
See if you can grok how the Visual Tree and Logical Tree works in WPF, once you understand that, you will understand more of how binding works.
I'd also recommend using the free tool Snoop to look at the Visual Tree. XAML Spy is excellent, but not free.
Snoop can tell you if there is anything that has a bad binding at runtime (you set the filters up, and it will list all bad bindings).
You can use Snoop to get the full XAML path of your source (the XAML you wrote above), then get the full XAML path of the target (i.e. the ActualWidth of your Grid), then compare them: it may be quickly apparent that one is not the ancestor of the other, as they are on different branches of the visual tree, or that there is some other issue which is preventing a simple walk up the visual tree from working.
If you just want to get something working, as a proof of concept, try naming the target XAML grid using x:Name, and reference it by name instead of AncestorType.

Assign a FrameworkElement to a control template programatically

I'm trying to custom draw a GridSplitter, and I have the following XAML code:
<GridSplitter Grid.Column="1" Width="50" HorizontalAlignment="Stretch">
<GridSplitter.Template>
<ControlTemplate TargetType="{x:Type GridSplitter}">
<custom:DiffSplitterCanvas />
</ControlTemplate>
</GridSplitter.Template>
</GridSplitter>
DiffSplitterCanvas inherits from Canvas.
I need to write it programatically. And I also need it to use a given already created instance of the DiffSplitterCanvas. Simplifying the code, it would be something like this:
GridSplitter mySplitter = new GridSplitter();
ControlTemplate myTemplate = new ControlTemplate(typeof(GridSplitter));
DiffSplitterCanvas myCanvas = new DiffSplitterCanvas();
AddElementToTemplate(myCanvas, myTemplate);
mySplitter.Template = myTemplate;
void AddElementToTemplate(FrameworkElement element, ControlTemplate template)
{
// how could achieve this?
}
EDIT: The reason I ask this question is because I need to change some properties in the DiffSplitterCanvas instance while the user interacts with the UI. Maybe this is not the way to implement it using WPF, but I'm really lost here.
The correct way is to use bindings and dependency properties. You could do this in pure procedural code, but this will be over-complicated and error-prone.
You can get an overview of dependency properties in the official documentation. There is also some good tutorials.
Assuming you declare a dependency property called MyProperty in DiffSplitterCanvas, in the end you will have something like:
<GridSplitter Grid.Column="1" Width="50" HorizontalAlignment="Stretch">
<GridSplitter.Template>
<ControlTemplate TargetType="{x:Type GridSplitter}">
<custom:DiffSplitterCanvas
MyProperty="{Binding Path=SomeOtherPropertyFromDataContext}"/>
</ControlTemplate>
</GridSplitter.Template>
</GridSplitter>
Whenever the value of SomeOtherPropertyFromDataContext changed it will be reflected on the DiffSplitterCanvas. Note that you will need to configure the Source attribute of the binding accordingly, depending on what is holding the SomeOtherPropertyFromDataContext property (might be the context of the column, the parent control, the view model, etc.).

WPF : Add border style to Image programmatically

I have few images in WPF. On mouse click event, I want to add a border to the image. Please tell me how to do it.
Should I have to create a style element in the xaml and apply it in the code-behind?
There are a lot of ways. I recommend something like this, using xaml.
<Border BorderThickness="2">
<Border.BorderBrush>
<SolidColorBrush Color="LightGray" Opacity="{Binding Path=IsSelected, Converter={StaticResource BooleanToDouble}}"/>
</Border.BorderBrush>
<Image Source="{Binding Path=ImageUri}"/>
</Border>
DataContext of this block must have IsSelected property or something like this. Also you have to implement a IValueConverter to convert true to 1 and false to 0.
Just remove the image from its container, create the border, add the image as the border's child, and add the border back to the container where the image was. If you get stuck, post code and I'll help you adapt it, but it shouldn't be difficult at all. You can do it all in the code-behind.

Categories