Bind template property value to templated control property - silverlight

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.

Related

Silverlight GetTemplateChild of control within control returns null

I'm trying to add an indicator (I'm using TextBlock) to the datepicker control.
Visually it works but I can't get the control via GetTemplateChild. I assume it's something to do with the fact that the TextBlock control I added is in the DatePickerTextBox style template as opposed to the DatePicker style template.
I've tried DefaultStyleKey (although I don't think this makes sense as it's the TextBox control within DatePicker that's the problem) and using OnApplyTemplate and UpdateLayout on the TextBox control.
Here's a snippet of the Dictionary.xaml
<Style x:Key="Ind_DatePickerTextBoxStyle" TargetType="primitives:DatePickerTextBox">
...
<Grid VerticalAlignment="Stretch">
<TextBlock x:Name="Indicator" Text="*" Style="{StaticResource IndicatorStyle}" Visibility="Collapsed"/>
...
<!--datepicker style snippet-->
<primitives:BF_DatePickerTextBox
x:Name="TextBox"
SelectionBackground="{TemplateBinding SelectionBackground}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
Grid.Column="0"
Style="{StaticResource Ind_DatePickerTextBoxStyle}" />
GetTemplateChild can only be used from "your control" to get a control defined in it's [control]template. When you have defined a control and given it a style you can use GetTemplateChild
public class MyCustomControl : Control
{
override OnApplyTemplate()
{
var textbox = GetTemplateChild("TextBox");
}
}
<Style TargetType="local:MyCustomControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyCustomControl">
<TextBox x:Name="TextBox"/>
</ControlTemplate>
</Setter.Value>
</Setter>
So in this example I was able to use GetTemplateChild to get the TextBox child inside the control because I was accesings my [control]template. I cannot use GetTemplateChild to get the TextBox from another control that uses MyCustomControl. Only MyCustomControl can use GetTemplateChild to get the TextBox.
Now I can extend MyCustomControl to do the same
public class MyOtherCustomControl : MyCustomControl
{
override OnApplyTemplate()
{
var textbox = GetTemplateChild("TextBox");
}
}
I hope this helps!

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"

How to get GridSplitter to move between extremes

I have a Gridsplitter in a vertical grid and ideally what would like to see two buttons in the GridSplitter. An up button would automatically move the splitter to the highest top position and a bottom button would move it all the way down. However, the GridSplitter cannot contain other items. Any thoughts on a way around this? I thought of just making a panel and then sandwiching it between two GridSplitters?
GridSplitter inherits from Control, so all you need to do is define a template for it that includes the two buttons:
<ControlTemplate x:Key="SplitterWithButtons" TargetType="{x:Type GridSplitter}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<DockPanel>
<Button DockPanel.Dock="Left" Content="{StaticResource UpArrow}" Click="OnSplitterUpButton" />
<Button DockPanel.Dock="Right" Content="{StaticResource DownArrow}" Click="OnSplitterDownButton" />
</DockPanel>
</Border>
</ControlTemplate>
...
<GridSplitter Template="{StaticResource SplitterWithButtons}" ... />
Inside your event handlers you can find the GridSplitter like this:
private void OnSplitterUpButton(object sender, RoutedEventArgs e)
{
var splitter = ((Button)sender).TemplatedParent as GridSplitter;
...
}

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

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.

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