WPF: How to properly override the methods when creating custom control - wpf

I am creating a custom control Toolbox that is derived from ItemsControl. This toolbox is supposed to be filled with icons coming from the database. The definition looks like this:
public class Toolbox : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ToolboxItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is ToolboxItem);
}
}
Toolboxitem is derived from ContentControl.
public class ToolboxItem : ContentControl
{
static ToolboxItem()
{
FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(ToolboxItem), new FrameworkPropertyMetadata(typeof(ToolboxItem)));
}
}
Since the number of icons stored in a database is not known I want to use the data template:
<DataTemplate x:Key="ToolBoxTemplate">
<StackPanel>
<Image Source="{Binding Path=url}" />
</StackPanel>
</DataTemplate>
Then I want the Toolbox to use the template.
<Toolbox x:Name="NewLibrary" ItemsSource="{Binding}" ItemTemplate="ToolBoxtemplate">
</Toolbox>
I'm using ADO.NET entity framework to connect to a database. The code behind:
SystemicsAnalystDBEntities db = new SystemicsAnalystDBEntities();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
NewLibrary.ItemsSource = from c in db.Components select c;
}
However, there is a problem. When the code is executed, it displays the object from the database (as the ItemSource property is set to the object from the database) and not the images. It does not use the template. When I use the static images source it works in the right way
I found out that I need to override the PrepareContainerForItemOverride method.But I don't know how to add the template to it.
Thanks a lot for any comments.
Additional Information
Here is the ControlTemplate for ToolboxItem:
<ControlTemplate TargetType="{x:Type s:ToolboxItem}">
<Grid>
<Rectangle Name="Border"
StrokeThickness="1"
StrokeDashArray="2"
Fill="Transparent"
SnapsToDevicePixels="true" />
<ContentPresenter Content="{TemplateBinding ContentControl.Content}"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="true">
<Setter TargetName="Border"
Property="Stroke"
Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

ToolboxItem is overriding the default style for ContentControl. You haven't posted the overridding style (from generic.xaml), but I suspect your problem is with the template defined in that style. Your ToolboxItem template needs to contain a ContentPresenter, e.g.:
<Style TargetType="{x:Type local:ToolboxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ToolboxItem}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Alternatively, if you don't need to do anything special in the ToolboxItem UI, just remove the DefaultStyleKeyProperty.OverrideMetadata call.
Note that you do not need to override PrepareItemForContainerOverride.

You have correctly implemented the methods. The problem is, as I suspected, in your ToolBoxItem ControlTemplate which you posted recently. If it had used an ordinary <ContentPresenter /> you would have been fine. You ran into ContentPresenter's "magic" properties which are only set automatically if you don't set any of them.
Here is the problem code in your ControlTemplate:
<ContentPresenter
Content="{TemplateBinding ContentControl.Content}"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
The problem is that you are setting the Content property but not setting the ContentTemplate property. ContentPresenter has custom code that automatically creates bindings for its Content, ContentTemplate, and ContentTemplateSelector properties, but only if the Content property is not set manually.
In your case, the Content property is being set manually, so the automatic mechanism is disabled and so ContentTemplate is null.
Although it would be possible to manually set all three automatic properties like this:
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
Your best choice is to omit them entirely and just accept ContentPresenter's default behavior, like this:
<ContentPresenter
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
Note: NET Framework 3.5 adds a fouth automatically-bound property, ContentStringFormat, which would be missed if you manually bound the three properties instead of letting ContentPresenter do it for you automatically.

Is this code directly copied from your project? If so, the following
ItemTemplate="ToolBoxtemplate"
should be:
ItemTemplate="{StaticResource ToolBoxTemplate}"
I'm not entirely sure, but you might need to set the ContentTemplate of your Toolbox container explicitly in PrepareContainerForItemOverride, since you may have overridden the behavior that automatically sets the template. You may need to set it as a Binding, as I'm not sure if the containers are re-generated if the ItemTemplate changes.

It looks like your problem could be that your url property is not exposed within ToolBoxItem. When your items are bound directly to ToolBox the url property is directly exposed to the DataTemplate.
In order for your example to work, ToolBoxItem would need to have:
public ImageTypeHere url { get; private set; }
If this is really a simple implementation it would probably benefit you more to use (or at least derive from) a ListBox and use a custom DataTemplate and Style for your ListBoxItems rather than creating your own control.

Related

ContentPresenter loosing it data binding after triggers invoked more than twice

I have created an extended button with 2 different border styles invoked by triggers in XAML. Both share the same contentpesenter but after changing the border style more than twice the content in the contentpresenter fails to display.
Below is a link to the entire project with a test bed application that demonstrates the issue, I think the issue is somewhere in the XAML below but I cannot see why it breaks:
Sample Button App
<Style.Resources>
<ContentPresenter x:Key="ButtonContent" Margin="5" HorizontalAlignment="Center"
Content="{Binding Content}"/>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Margin="{Binding KeyMargin}">
<Grid Visibility="{Binding RectangleVisibility}">
<Grid.OpacityMask>
<VisualBrush Visual="{Binding ElementName=rectBorder}" />
</Grid.OpacityMask>
<Border x:Name="rectBorder"
CornerRadius="{Binding BorderCorners}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{Binding BorderThickness}"/>
<Viewbox Stretch="Fill"
StretchDirection="Both">
<ContentControl Content="{StaticResource ButtonContent}"/>
</Viewbox>
</Grid>
<Grid Visibility="{Binding EllipseVisibility}">
<Ellipse Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{Binding BorderThickness}"
Fill="{TemplateBinding Background}">
</Ellipse>
<Viewbox Stretch="Fill"
StretchDirection="Both">
<ContentControl Content="{StaticResource ButtonContent}"/>
</Viewbox>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
The problem is most likely that you cannot have the same element (the ContentPresenter in this case) in more than one place in the visual tree, and in which one of the two grids it ends up is undefined, i.e., an implementation archetype of WPF.
To get the element duplicated this might work:
<ContentControl Content="{TemplateBinding Content}"/>
or in your case
<ContentControl Content="{TemplateBinding Content}" Margin="5" HorizontalAlignment="Center"/>
instead of a static resource. The <ContentPresenter/> syntax is pretty much an optimized shortcut for that (or you could set x:Shared="False" on the resource, but having a ContentPresenter as a static resource is as far as I know not how it is intended to be used)
If the Button content is a UIElement itself though, it will be used directly itself in the visual tree, i.e., twice and this wont work either. A better solution would be to just have the content once in the control template and change the visual appearance around it, e.g., using a trigger to set the Grid's OpacityMask.
Another remark is that your control template is very tightly bound to where the Button is used, with direct bindings to the current data context, which reduces its reusability. Some easy fixes is to use TemplateBinding instead of Binding for BorderThickness respectively Margin (instead of KeyMargin), since those are existing properties of the Button.
For better reusability and cleaner code you should consider looking into creating a custom control deriving from Button with dependency properties for BorderCorners, the desired visual state (ellipse vs rectangle) etc. You might also want to use triggers to get the mouse-over effects of the button etc. Have fun control templating!

Attached Properties

I am a little confused about WPF attached properties. When you use an attached property that attached property can only be read and used by the class that defines it correct? For example if I wanted to use some attached property as a hover color on a button, can I get the attached property value from the button's template, and will I be able access the attached property from the button to set the hoover color?
Adding to the answer from H.B. using an example:
For example if I wanted to use some attached property as a hover
color on a button, can I get the attached property value from the
button's template, and will I be able access the attached property
from the button to set the hover color?
Yes, you sure can. Say that you have an Attached Property called HoverBrush defined in a class called SomeClass, you can set the value on the instance and bind to it in the template
<StackPanel>
<StackPanel.Resources>
<ControlTemplate x:Key="MyButtonTemplate" TargetType="{x:Type Button}">
<Border x:Name="border" Background="Gray">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border"
Property="Background"
Value="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=(local:SomeClass.HoverBrush)}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</StackPanel.Resources>
<Button Content="Blue Hover"
local:SomeClass.HoverBrush="Blue"
Template="{StaticResource MyButtonTemplate}"/>
<Button Content="Green Hover"
local:SomeClass.HoverBrush="Green"
Template="{StaticResource MyButtonTemplate}"/>
</StackPanel>
The attached property in question is defined like this
public class SomeClass
{
public static DependencyProperty HoverBrushProperty =
DependencyProperty.RegisterAttached("HoverBrush",
typeof(Brush),
typeof(SomeClass),
new PropertyMetadata(null));
public static void SetHoverBrush(DependencyObject obj, Brush value)
{
obj.SetValue(HoverBrushProperty, value);
}
public static Brush GetHoverBrush(DependencyObject obj)
{
return (Brush)obj.GetValue(HoverBrushProperty);
}
}
Have you read the overview? If not, do it.
Attached properties, like dependency properties, just register another key that can be used in the properties dictionary of controls. You can set values anywhere and you can retrieve them anywhere, they are not restricted by type. This means that you may only want it to be set on Buttons but it can be set on TextBoxes too.
Every control has its own dictionary of property keys and values, an attached property allows you to write a value to those dictionaries using a new key. As those dictionaries are independent they can have separate values for the same property which is set and accessed via the static field property declaration.
As those properties are attached you will have to get the values via the GetValue (as the classes cannot provide a CLR-wrapper themselves).

Bind template property value to templated control property

The title is a bit fuzzy, the problem is:
I'm implementing a Silverlight 4 button by swapping the template with my own. Is it possible to bind the border corner radius to the button height?
For example the user can set the height in designer to be 30, then the corner radius of the template inner border should be 15. When height is 50, then corner radius should be 25, etc.
If possible, I need a pure XAML solution.
Thanks
The is no pure Xaml solution. Ultimately you need something to at the very least perform the expression y/2 and that is not something currently offered by any existing Xaml based component.
Open the project in Vs2010. Add a new item... "Silverlight Templated Control" call it "RoundEndedButton".
Replace the source with:-
public class RoundEndedButton : Button
{
public RoundEndedButton()
{
this.DefaultStyleKey = typeof(RoundEndedButton);
SizeChanged += new SizeChangedEventHandler(RoundEndedButton_SizeChanged);
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(
"CornerRadius",
typeof(CornerRadius),
typeof(RoundEndedButton),
new PropertyMetadata(new CornerRadius()));
void RoundEndedButton_SizeChanged(object sender, SizeChangedEventArgs e)
{
SetValue(CornerRadiusProperty, new CornerRadius(e.NewSize.Height / 2));
}
}
In the themes/Generic.xaml modify its default template. Here is my very simple example:-
<Style TargetType="local:RoundEndedButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:RoundEndedButton">
<Border x:Name="Background"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter
x:Name="contentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note the use of the extra CornerRadius property in template binding. Of course now you switch back to blend add this control to a surface and get creative on the style.

How to hide the navigation bar in a WPF page

I want to hide the navigation bar in a page created using WPF. I have tried ShowsNavigationUI = false, but it is still displaying the control.
Specify to the page Container, the intention to not have a navigation bar, using
NavigationUIVisibility property.
<Frame NavigationUIVisibility="Hidden" Panel.ZIndex="1" ... />
It's a very easy implementation.
<Frame x:Name="_FrameName" NavigationUIVisibility="Hidden" />
Setting ShowsNavigationUI=False on a Page ought to do it. There does seem to be a bug, however, that will cause this to fail in at least one sequence of events:
Page is already in NavigationWindow when this is set
Page is navigated away and back again
There may be other scenarios I haven't run into yet that make it fail.
To get this to work totally reliably, what I do is ignore the Page.ShowsNavigationUI property entirely and set it instead on NavigationWindow. This seems to be completely reliable.
Here is how this can be done in your Page constructor:
Dispatcher.BeginInvoke(ApplicationPriority.Render, new Action(() =>
{
var navWindow = Window.GetWindow(this) as NavigationWindow;
if(navWindow!=null) navWindow.ShowsNavigationUI = false;
}));
If you do this, remember not to set ShowsNavigationUI on any Page object.
FYI, you can also restyle your NavigationWindow any way you like by changing its ControlTemplate. For example this removes everything but the actual page content:
<Style TargetType="{x:Type NavigationWindow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type NavigationWindow}">
<AdornerDecorator>
<ContentPresenter Name="PART_NavWinCP"
ClipToBounds="true"/>
</AdornerDecorator>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If you're using a Frame you can change the Frame's default style to remove the navigation buttons (shown below). The same approach could be done for NavigationWindow. I originally tried setting Page.ShowsNavigationUI and it had no effect. Just add the below style to a ResourceDictionary and it works fine.
<Style TargetType="{x:Type Frame}">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Frame}">
<Border BorderThickness="{TemplateBinding Border.BorderThickness}" Padding="{TemplateBinding Control.Padding}" BorderBrush="{TemplateBinding Border.BorderBrush}" Background="{TemplateBinding Panel.Background}">
<ContentPresenter Content="{TemplateBinding ContentControl.Content}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" Name="PART_FrameCP" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This one I found really easy. In your MainWindow, do this:
public MainWindow()
public partial class MainWindow : NavigationWindow
{
public MainWindow()
{
InitializeComponent();
ShowsNavigationUI = false;
}
}
}
And if you have an event on button click to open a new page, just do this:
private void btnEndUserSearch_Click(object sender, RoutedEventArgs e)
{
EndUser EndUserSearchPage = new EndUser();
this.NavigationService.Navigate(EndUserSearchPage);
EndUserSearchPage.ShowsNavigationUI = false;
}
Above works only for Navigation windows, but I am using ordinary WPF windows. Some say these are better than Navigation windows. I am using DockPanel to host my pages. My solution creates a new template for the DockPanel and simply does not add buttons or makes them hidden (see StackPanel Visibility="Hidden"). It works nicely.
<DockPanel>
<Frame x:Name="_mainFrame">
<Frame.Template>
<ControlTemplate TargetType="Frame">
<DockPanel Margin="7">
<StackPanel Visibility="Hidden"
Margin="0"
Orientation="Horizontal"
DockPanel.Dock="Top"
>
<!--<Button
Content="Avast! Go back!"
Command="{x:Static NavigationCommands.BrowseBack}"
IsEnabled="{TemplateBinding CanGoBack}"
/>
<Button
Content="Forward you dogs!"
Command="{x:Static NavigationCommands.BrowseForward}"
IsEnabled="{TemplateBinding CanGoForward}"
/>-->
</StackPanel>
<Border>
<ContentPresenter />
</Border>
</DockPanel>
</ControlTemplate>
</Frame.Template>
</Frame>
</DockPanel>
One of the easiest and simplest way is to add:
ShowsNavigationUI = false;
in your cs file constructor under InitializeComponent();
For beginners, you can simply change code at MainWindow.xaml like this.
<Window>
<Grid >
<Frame x:Name="LeftFrame" NavigationUIVisibility="Hidden"/>
<Frame x:Name="RightFrame" NavigationUIVisibility="Hidden"/>
</Grid>
</Window>
I had this problem whenever I dynamically changed the Content property of a Frame, and solved it by using the following code in my click() event.
ContentFrame.NavigationUIVisibility = NavigationUIVisibility.Hidden;
Where ContentFrame is the name of the frame, as defined in XAML. i.e.
<Frame x:Name="ContentFrame" />
On the NavigationWindow itself I use ShowsNavigationUI="False"

Silverlight: TemplateBinding a Rectangle

I'm having a heck of a time trying to template bind the StrokeThickness of a rectangle.
My goal is to allow a user of my custom control to set a property called SelectedBorderThickness which will, in fact, set the StrokeThickness of a rectangle.
I thought I understood templating but I guess I really don't.
If I do this:
<Rectangle x:Name="myRect" Height="100" Width="100" Stroke="Black" SelectedBorderThickness="5" />
Can someone please show me how to write the Style elements to get this to work?
You should add more details to the question and people will be able to help you more easily. I think I have figured out what you want though.
You are looking to make a custom templated silverlight control, containing a bunch of elements incluiding a rectangle in its template. You would like a user to be able to set the thickness of that rectangle inside the control with a property on the control itself. From what you put above, I don't know how much you have written in your code -- so I will just post a nearly complete example of what you are after.
First I created a templated custom control in visual studio, and added the new dependancy property we want a user to be able to set:
public class TestControl : Control
{
static public DependencyProperty SBTProperty { get; set; }
static TestControl()
{
SBTProperty = DependencyProperty.Register("SelectedBorderThickness", typeof(double), typeof(TestControl),null);
}
public TestControl()
{
this.DefaultStyleKey = typeof(TestControl);
}
public double SelectedBorderThickness
{
get { return (double)GetValue(SBTProperty); }
set { SetValue(SBTProperty, value); }
}
}
Then I set up the template in Generic.xaml (for my example the only thing I have in my control is the rectangle since I don't know what you want in there):
<Style TargetType="local:TestControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TestControl">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Rectangle Fill="Bisque" Stroke="Black" StrokeThickness="{TemplateBinding SelectedBorderThickness}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now I am all set up to use it from xaml in other parts of my application. For my example, I put one right in the center of my MainPage:
<local:TestControl SelectedBorderThickness="75"></local:TestControl>
EDIT:
After reading your code below, I see now what the problem is. You're trying to do a template binding, but the way you have it it's going to try to bind to the current template, which is the template for listboxitem and not your custom listbox. What you really want in this situation is to do a RelativeBinding with FindAncestor to jump up the tree to the template of your custom listbox, but MS hasn't yet implemented that kind of binding in Silverlight (even though it's pretty common in WPF). Luckily in your specific situation we can finagle the right object through the path in a TemplatedParent binding, without having to write a bunch of messy codebehind to emulate an ancestor binding:
StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Parent.SelectedBorderThickness}"
Dump that into the Rectangle in your template posted above and it should work -- it will access the content of the ListBoxItem (which is whatever you are displaying), and then access that objects Parent (which will be your custom listbox). From there we just hit up the property we set up before.
If you want a cleaner solution, join the chorus of us asking MS to implement ancestor binding in Silverlight.

			
				
Here's the problem section, it's when I'm attempting to style the ItemContainerStyle for my custom control which derives from a ListBox:
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Background="{TemplateBinding Background}">
<!-- VSM stuff removed for clarity -->
<ContentPresenter
x:Name="contentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"/>
<Rectangle x:Name="FocusVisualElement"
Stroke="Goldenrod"
StrokeThickness="{TemplateBinding SelectedBorderThickness}"
Visibility="Collapsed"
RadiusX="1"
RadiusY="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
The problems is that when I set StrokeThickness = {TemplateBinding SelectedBorderThickness} on the Rectangle and then try to use the control in a test app, I get a ParserError:
Message: Unknown attribute StrokeThickness on element Rectangle
If I hardcode the StrokeThickness to 3 (or whatever), it parses fine and I can view the test app.
In the end, all I'm really trying to do is create a property that shows up in Intellisense so that my (eventual) end users of my custom control can change the color and border thickness, radius, etc. of the highlight on a hovered and selected ListBoxItem in a dynamically bound custom ListBox. It shouldn't be this dang hard.
The dang comments are too restricted. I'm not trying to answer my own question (I wish I could).
David, your code works fine when you add ListBoxItems statically. When adding them dynamically, the thickness doesn't change. To test this out, I added a new TestControl in MainPage:
<StackPanel>
<local:TestControl SelectedBorderThickness="9" x:Name="h1n1">
<TextBlock Text="Honk1"></TextBlock>
<TextBlock Text="Honk2"/>
</local:TestControl>
<local:TestControl x:Name="SwineFlu" SelectedBorderThickness="20" />
</StackPanel>
In the code-behind I added:
ObservableCollection<string> test = new ObservableCollection<string>();
test.Add("Hi David");
test.Add("Hello World");
SwineFlu.ItemsSource = test;

Resources