A bit unsual way to change look of a custom ContentControl - wpf

I might be just missing something or probably today is not my day but what I am trying to do keeps failing.
I have a custom control called MyContentControl. It looks like this:
public class MyContentControl : ContentControl
{
static MyContentControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyContentControl), new FrameworkPropertyMetadata(typeof(MyContentControl)));
}
public DockPanel DifferentLook
{
get;
set;
}
public string Txt
{
get;
set;
}
protected override void OnInitialized(EventArgs e)
{
if (this.DifferentLook != null)
{
this.Content = this.DifferentLook;
}
Binding b = new Binding("Txt");
b.Source = this;
this.SetBinding(ContentProperty, b);
base.OnInitialized(e);
}
}
This is its theme:
<Style TargetType="{x:Type local:MyContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyContentControl}">
<Border Background="{TemplateBinding Background}" Margin="{TemplateBinding Padding}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<ContentPresenter VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is MainWindow:
<local:MyContentControl Txt="texty text">
<local:MyContentControl.DifferentLook>
<DockPanel>
<TextBlock Text="Content = " DockPanel.Dock="Left"/>
<ContentPresenter DockPanel.Dock="Left"/>
</DockPanel>
</local:MyContentControl.DifferentLook>
</local:MyContentControl>
When no "DifferentLook" specified I would like the control to use the ContentPresenter defined inside the default ControlTemplate.
If I have DifferentLook set then it should display the control in different look.
See the method OnInitalized.
Now the problem is when I apply DifferentLook it seems that the DifferentLook.ContentPresenter is not working.
Why is DifferentLook.ContentPresenter not appyling the content correctly?
The output on window is "texty text" but it should be "content = texty text".
EDIT: This is the light version. I created this and kept things as simple as possible to demostrate the problem. In real the custom control is a bit huge and user may not override ControlTemlates.
Do you guys have an idea how to solve this with the given requirements?

Buddy I am not quite agreed with the approach you are taking here. But I can point out what you are doing wrong and why you are not able to see your DockPanel.
Since you have defined the ControlTemplate for your ContentControl having ContentPresenter, then any Content you set on your ContentControl will be placed inside the ContentPresenter of your ControlTemplate defined in style whether it is the Content set by Binding or Directly placed inside on control (like DockPanel here)
In your example, OnInitialized() function first set the Content to the DockPanel and then immediatly set Binding on Content. The moment Binding is set it will override the previously set DockPanel with the Source value which is string Txt(texty text).
So if you remove the Binding setting from Oninitialized you will see your DockPanel. But wait there is one more thing. Inside the Content of your ContentPresenter with Content bound as Content="{Binding}" which will make your programme to go into infinite recursion as it will try to bind it to self and on and on. So you will have to set the Content Binding of inner ContentPresenter to some property of Parent ContentControl Context. So just for testing try removing the content binding from inner contentpresenter and give some static value.

Related

In WPF, how do I override style set by the template at the instance level?

So I have a custom template for scrollbar. It work great, but I would like to override the corner rounding value. On the template, it is set like this:
<ControlTemplate x:Key="VerticalScroll" TargetType="{x:Type ScrollBar}">
<Grid>
<Border Grid.RowSpan="3" CornerRadius="3" BorderBrush="DarkBlue" BorderThickness="1" Opacity=".6"></Border>
I am creating my instance like this:
<ScrollViewer Padding="0,0,0,0">
<TextBlock Height="23" HorizontalAlignment="Stretch" Margin="0" Name="textBlock1" Text="TextBlock" VerticalAlignment="Top" />
</ScrollViewer>
How do I change the CornerRadius to 6?
I don't think there's any easy way to do this, since there is no template inheritance in WPF. You would either have to copy your entire template and modify it for this particular instance, or set the template in Styles
Use an Attached DependencyProperty. The Attached DependencyProperty allows you to store information and against a DependencyObject.
So, create an Attached DependencyProperty, in the following case, against the MainWindow.
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.RegisterAttached("CornerRadius", typeof(int), typeof(MainWindow));
public static void SetCornerRadius(DependencyObject element, int value)
{
element.SetValue(CornerRadiusProperty, value);
}
public static int GetCornerRadius(DependencyObject element)
{
return (int) element.GetValue(CornerRadiusProperty);
}
Then in the Xaml, on the ScrollViewer assign the value
<ScrollViewer Padding="0,0,0,0" this:MainWindow.CornerRadius="20" ... >
and in the template, reference the Attached DependencyProperty using a TemplatedParent binding.
<ControlTemplate x:Key="ScrollViewerTemplate" TargetType="{x:Type ScrollViewer}">
<Grid>
<Border Grid.RowSpan="3" CornerRadius="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(this:MainWindow.CornerRadius)}" BorderBrush="DarkBlue" BorderThickness="1" Opacity=".6">
I hope this helps.
In case its only property you interested to set from outside, you can store value in Tag and do TemplateBinding with Tag.
<Border CornerRadius="{TemplateBinding Tag}"/>
and in instance ScrollViewer:
<ScrollViewer Tag="6"/>
However, if there are more properties you want to set from outside, either subclass ScrollViewer and create your custom DP's or you can also go with attached properties in case not interested in subclassing of ScrollViewer.

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 bind to parent position changes in WPF using XAML and no code behind

I have a visio-like interfact but have actual model data behind some of the elements. The elements can be moved by the user.
I use a contentcontrol on a canvas whereby the viewmodels of the elements are places in the content which can then be displayed differently depending on their type but using the same contentcontrol. It is simple to bind the view to the different properties in the viewmodel. However, I have to save the position in the model, and I cannot find a binding solution.
1) The Application.Save Command is handled in the main view model, so I do not have access to the view there. That means I must save the postion data when the elements are moved, or is there a better approach?
2) Assuming that I am right with 1), I am looking to avoid code behind, i.e. I do not want the contentcontrol to deal with the elements that they have in their content. However, so far the code behind version is all I could come up with:
My code behind solution so far:
All model elements implement an interface:
public interface IViewElement
{
String Position { get; set; }
}
And in the contentcontrol:
void ContentControl_LayoutUpdated(object sender, EventArgs e)
{
IViewElement content = this.Content as IViewElement;
content.Position = new Point(Diagram.GetLeft(this), Diagram.GetTop(this)).ToString();
}
The XAML:
<Style TargetType="{x:Type diagram:Item}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type diagram:Item}">
<Grid Canvas.Top="{Binding ElementName=PART_ContentPresenter, Path=Content.Position, Mode=TwoWay}" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
ContextMenu="{x:Null}">
<!-- PART_ContentPresenter -->
<ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<DataTemplate DataType="{x:Type local:ViewModel}">
<StackPanel>
...
</StackPanel>
Just encapsulate the codebehind you've used in a Behavior
Why are you using a string to store the position? Use either a Point or two decimal values, and then bind your ContentControl's Canvas.Top and Canvas.Left position to these values using two-way binding.
It will automatically update the model when the Top and Left positions change.
Edit:
Here's an example:
<ContentControl Canvas.Top="{Binding ContentModel.Top, Mode=TwoWay}"
Canvas.Left="{Binding ContentModel.Left, Mode=TwoWay}"
Content="{Binding ContentModel}" />

Defining InputBindings within a Style

I'm creating a WPF app using the MVVM design pattern, and I'm trying to extend the TabItem control so that it closes the tab when the user clicks the middle mouse button. I'm trying to achieve this using InputBindings, and it works very well until I try to define it within a style. I've learned that you cannot add InputBindings to a style unless you attach it using a DependencyProperty. So I followed this similar post here... and it works... almost. I can close one tab using the middle mouse button, but it won't work on any of the other tabs (all of the tabs are added at runtime and inherit the same style).
So I need some help. Why would this only be working the first time, and not after? Obviously I could create a custom control that inherits from a TabItem and make it work, but I'd like to figure this out as I can see this being expanded in my projects. I'm no expert on DependencyProperties, so please help me out. Thanks!
Style:
<Style TargetType="{x:Type TabItem}">
<Setter Property="w:Attach.InputBindings">
<Setter.Value>
<InputBindingCollection>
<MouseBinding MouseAction="MiddleClick"
Command="{Binding CloseCommand}"/>
</InputBindingCollection>
</Setter.Value>
</Setter>
...
</Style>
Class
public class Attach
{
public static readonly DependencyProperty InputBindingsProperty =
DependencyProperty.RegisterAttached("InputBindings", typeof(InputBindingCollection), typeof(Attach),
new FrameworkPropertyMetadata(new InputBindingCollection(),
(sender, e) =>
{
var element = sender as UIElement;
if (element == null) return;
element.InputBindings.Clear();
element.InputBindings.AddRange((InputBindingCollection)e.NewValue);
}));
public static InputBindingCollection GetInputBindings(UIElement element)
{
return (InputBindingCollection)element.GetValue(InputBindingsProperty);
}
public static void SetInputBindings(UIElement element, InputBindingCollection inputBindings)
{
element.SetValue(InputBindingsProperty, inputBindings);
}
}
Your class "Attach" worked fine for me!
If anyone needs, the trick is use style like this, with the x:Shared modifier:
<InputBindingCollection x:Key="inputCollection" x:Shared="False">
<KeyBinding Key="Del" Command="{Binding DeleteItemCommand}"/>
</InputBindingCollection>
<Style TargetType="{x:Type TabItem}">
<Setter Property="w:Attach.InputBindings" Value="{StaticResource inputCollection}" />
...
</Style>
Thanks!
Never mind, I figured it out myself. I ended up not even using the Attach class above... instead I used InputBindings on the ControlTemplate for the TabItem (which is a Border), so it looked something like this... I don't know why I didn't think of this in the first place.. :)
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid SnapsToDevicePixels="true">
<Border x:Name="Bd" ...>
<DockPanel>
...
</DockPanel>
<Border.InputBindings>
<MouseBinding MouseAction="MiddleClick"
Command="{Binding CloseCommand}"/>
</Border.InputBindings>
</Border>
</Grid>
...
</ControlTemplate>

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