WPF custom Textbox control isn't properly binding text - c#

I have a custom TextBox control. I am trying to bind it to a simple Description string property of an object. How can I get the binding to work? The same binding works fine if I change this to a TextBox.
<Style x:Key="{x:Type TaskDash:TextBoxWithDescription}" TargetType="{x:Type TaskDash:TextBoxWithDescription}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TaskDash:TextBoxWithDescription}">
<TextBox>
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public class TextBoxWithDescription : TextBox
{
public TextBoxWithDescription()
{
LabelText = String.Empty;
}
public string LabelText { get; set; }
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var textBlock = (TextBlock)this.Template.FindName("LabelText", this);
if (textBlock != null) textBlock.Text = this.LabelText;
}
}
<TaskDash:TextBoxWithDescription Grid.Row="0" Grid.Column="0"
x:Name="textBoxDescription"
Text="{Binding Description, BindsDirectlyToSource=True}" LabelText="Description">
</TaskDash:TextBoxWithDescription>
public partial class EditTaskItem : Window
{
private TaskItem _taskItem;
public EditTaskItem(TaskItem taskItem)
{
InitializeComponent();
this.DataContext = taskItem;
textBoxDescription.DataContext = taskItem;
_taskItem = taskItem;
}
}

Ok ... there are a couple of things wrong with your code.
Lets begin with your style for your custom control. You need to add a static constructor which allows restyling your new control. Like this
static TextBoxWithDescription ()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBoxWithDescription ), new FrameworkPropertyMetadata(typeof(TextBoxWithDescription )));
}
This tells WPF "Hey, please look for a Style for this control".
Now you can remove the x:Key="{x:Type TaskDash:TextBoxWithDescription}"because this is going to be your default style.
Next thing is. In WPF one thing to understand is, that every control has absolutely no UI content, unless it gets an Template. You already have a Template in your Style, but the only visual content it gets is an empty TextBox. This is strange, because you derive your TextBoxWithDescription from TextBox. So what you create is a a control derived from textbox, containing a textbox.
See this to see how a TextBox Template looks in WPF 4.0.
Back to your empty TextBox in your ControlTemplate. Remember that i said, your controls, without a style are completely invisible, its only logic. The only visible thing is the TextBox in your Template. To make it work somehow, you need to pass some properties to this TextBox. The control says how and the template takes the properties and puts them in use.
You can do this via TemplateBinding
For example. If your control has a Property Background, this property does nothing you can set it as long as you want, but a ControlTemplate can give some meaning to it. So for example add a Rectangle in your ControlTemplate and set the Fill Property to {TemplateBinding Background}. Which basicaly tells the Rectangle "Your property Fill is going to be the value of Background from the control we are currently templating". I hope this makes it clear.
Next thing: You overrid OnApplyTemplate you usually do this to find a control in your ControlTemplate by name. It seems you mixed this with one of your properties. To Find a control in your Template via FindName, you need to give it a name
Like this
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TaskDash:TextBoxWithDescription}">
<TextBox x:Name="PART_MyTextBox">
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
and modify your OnApplyTemplate to
var textBlock = (TextBlock)this.Template.FindName("PART_MyTextBox", this);
Now you have the textblock in your current ControlTemplate.
The last mistake i can see is.
You set in OnApplyTemplatethe TextBox Text to your LabelText. While this works, one time, it is not very nice and usually not the WPF way. Also if you modify the LabelText property, it will not be displayed, because you would have to set it again.
Change your LabelText to a dependency property Now you can use the already mentioned TemplateBinding to set this text, directly on your control template TextBox.
<TextBox x:Name="PART_MyTextBox" Text="{TemplateBinding LabelText}>
</TextBox>
This will also make sure that changes to your Control property, will also update the Text on this TextBox.
One last thing
this.DataContext = taskItem;
// textBoxDescription.DataContext = taskItem;
_taskItem = taskItem;
If your textboxdescription is going to be a parent of your window, you don't need to set the DataContext explicitly, because it will be inherited down the hierachy. As long as an Element don't change the DataContext, it will be always the DataContext of the parent.
I would suggest you read more on the basics of WPF, i know it has a steep learning curve, but its worth the effort. It is difficult if someone comes from a WinForms background to get the head wrapped around all the different new design philosophies and different ways to do things.
Hope that helps a bit

#dowhilefor - great answer. I write this as an answer only because it's going to be too long for a comment.
#Shawn - it seems like you are trying to make a Label-Field control. If that's the case, try this template:
<ControlTemplate TargetType="{x:Type TaskDash:TextBoxWithDescription}">
<Grid>
<Grid.ColumnDefinitions>
<!--The SharedSizeGroup property will allow us to align all text boxes to the same vertical line-->
<ColumnDefinition Width="Auto"
SharedSizeGroup="LabelColumn"/>
<!--This column acts as a margin between the label and the text box-->
<ColumnDefinition Width="5"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{TemplateBinding LabelText}"
VerticalAlignment="Center"/>
<Border Grid.Column="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer x:Name="PART_ContentHost"
Padding="{TemplateBinding Padding}"/>
</Border>
</Grid>
</ControlTemplate>
And remove the override for OnApplyTemplate.
If the control is a part of a (often called) "PropertyGrid" and you have multiple instances of the TextBoxWithDescription control in the same panel, define Grid.IsSharedSizeScope on that panel (it doesn't have to be Grid panel). This will align the text boxes to a uniform vertical line.

Related

How can I access a named element of a ControlTemplate through code-behind?

I want to access one of the named elements within the original control template that another element is using, in the code-behind.
This is an example of the XAML code (obviously the original is more complicated, or I'd just be doing this in XAML):
<Window x:Class="Temp.MainWindow" Title="MainWindow">
<Window.Resources>
<ControlTemplate x:Key="MyTemplate" TargetType="{x:Type Expander}">
<Expander Header="Some header">
<StackPanel>
<Grid Name="MyGrid"/>
</StackPanel>
</Expander>
</ControlTemplate>
</Window.Resources>
<Grid>
<Expander Name="expander" Template="{DynamicResource MyTemplate}"/>
</Grid>
</Window>
What I've tried:
public MainWindow()
{
InitializeComponent();
Grid grid = expander.Template.FindName("MyGrid", expander) as Grid;
}
I've also tried
Grid grid = expander.Template.Resources.FindName("MyGrid") as Grid;
But g is always null.
I've looked at:
How do I access an element of a control template from within code behind?
How to access a WPF control located in a ControlTemplate
How do I programmatically interact with template-generated elements Part I
The links above are how I got the code I'm working with, but for some reason, g is just always null. Am I doing something wrong with the ContentTemplate? Any help would be appreciated!
You need to wait until the template is applied to the control
protected override OnApplyTemplate()
{
Grid grid = Template.FindName("YourTemplateName") as Grid;
}
The real problem here is that you're mixing technologies. You're attempting to use something meant for grabbing the template of a lookless control, in the behind code of the main window. I would be surprised if you didn't run into more issues.
Instead, I would suggest looking into How to Create Lookless Controls and redesigning your application. It wouldn't take much effort and it would all play nice together.

Create a DockPanel control with secondary content

I would like to create a custom control that provides all the functionality of the DockPanel, but it also exposes a secondary Overlay that is "outside" of the DockPanel. There would be a dependency property that will control the visibility of the the overlay panel, such that when the property is set true/visible, the Panel will appear overlayed on top of everything within the DockPanel.
Ideally the consumer would be able to drop the control into the same situation as a normal DockPanel, and with no other changes it would behave just like the normal DockPanel:
<DockPanelWithOverlay LastChildFill="True" >
<Button DockPanel.Dock="Bottom".../>
<Button DockPanel.Dock="Top".../>
<Grid>
<Grid controls.../>
</Grid>
</DockPanelWithOverlay>
However, there would be available the secondary area into which they could place the additional content and invoke when required.
<DockPanelWithOverlay LastChildFill="True" >
<Button DockPanel.Dock="Bottom".../>
<Button DockPanel.Dock="Top".../>
<Grid>
<Grid controls.../>
</Grid>
<DockPanel.Overlay>
<whatever controls for the overlay>
</DockPanel.Overlay>
</DockPanelWithOverlay>
But that wouldn't be valid since the Content is being set twice? So to cope, when using the overlay I guess I would have to explicitly state what goes where?:
<DockPanelWithOverlay LastChildFill="True" >
<DockPanel.Children>
<Button DockPanel.Dock="Bottom".../>
<Button DockPanel.Dock="Top".../>
<Grid>
<Grid controls.../>
</Grid>
</DockPanel.Children>
<DockPanel.Overlay Visibility="{Binding IsVisible}">
<whatever controls for the overlay>
</DockPanel.Overlay>
</DockPanelWithOverlay>
I'm not entirely sure the best way to tackle this: whether to create a CustomControl, or a UserControl, inherit directly from the DockPanel and try to expose a separate ContentControl, or maybe inherit from Panel and delegate the MeasureOverride and ArrangeOverride to the DockPanel.
How should I tackle this?
Interesting question. I wrote a DockPanelWithOverlay component that does the work:
I chose here the CustomControl because I wanted to have inheritance of Panel.
But Panel doesn't have a template it can change.
So I wrote a Custom Control inheriting of Control with a custom template
But a Usercontrol would perfectly work I think (I didn't try to be honest)
Edit UserControl is not so good, because it inherits of ContentControl.
So it can only have one children.
The goal of the DockPanelWithOverlay is to have many children.
So I think UserControl is not the best inheritance, as often.
UserControl is better when you want to provide some content in xaml, mostly static, not customizable by user of control.
End of edit
To organize content inside the tempalte, I used a Grid.
Order of the two components matters.
It is the drawing order.
Grid allows to put two components at the same place :
Inside there'll be the Overlay control, and a underlying DockPanel.
DockPanelWithOverlay
..|
..|-ControlTemplate
......|
......|-Grid
..........|
..........|--DockPanel
..........|--OverlayControl
Having a template is easier to make some binding from the DockPanelWithOverlay to the template's controls properties.
(To generate a CustomControl, create a WPFCustom Control Library Project)
Excerpt of themes\generic.xaml in library :
<Style TargetType="{x:Type local:DockPanelWithOverlay}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DockPanelWithOverlay}">
<!-- the grid allows to put two components at the same place -->
<Grid >
<DockPanel x:Name="dockPanel" />
<ContentControl x:Name="overlayControl" Visibility="{TemplateBinding OverlayVisibility}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Inheriting of control allows to use the template to create the small UIElements hierarchy.
Some dependency properties must be added for allowing binding :
Overlay for providing some UIElements, or a string for overlay content
OverlayVisibility for hiding/showing the overlay
Here is the code for the DockPanelWithOverlay :
(Note the OnApplytemplate called just after the templates componenets are called)
// Children is the property that will be valued with the content inside the tag of the control
[ContentProperty("Children")]
public class DockPanelWithOverlay : Control
{
static DockPanelWithOverlay()
{
// Associate the control with its template in themes/generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(typeof(DockPanelWithOverlay), new FrameworkPropertyMetadata(typeof(DockPanelWithOverlay)));
}
public DockPanelWithOverlay()
{
Children = new UIElementCollection(this, this);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// once the template is instanciated, the dockPanel and overlayCOntrol can be found from the template
// and the children of DockPanelWithOverlay can be put in the DockPanel
var dockPanel = this.GetTemplateChild("dockPanel") as DockPanel;
if (dockPanel != null)
for (int i = 0; i < Children.Count; )
{
UIElement elt = Children[0];
Children.RemoveAt(0);
dockPanel.Children.Add(elt);
}
}
// Here is the property to show or not the overlay
public Visibility OverlayVisibility
{
get { return (Visibility)GetValue(OverlayVisibilityProperty); }
set { SetValue(OverlayVisibilityProperty, value); }
}
// Here is the overlay. Tipically it could be a Texblock,
// or like in our example a Grid holding a TextBlock so that we could put a semi transparent backround
public Object Overlay
{
get { return (Object)GetValue(OverlayProperty); }
set { SetValue(OverlayProperty, value); }
}
// Using a DependencyProperty as the backing store for OverlayProperty.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty OverlayProperty =
DependencyProperty.Register("Overlay", typeof(Object), typeof(DockPanelWithOverlay), new PropertyMetadata(null));
public static readonly DependencyProperty OverlayVisibilityProperty =
DependencyProperty.Register("OverlayVisibility", typeof(Visibility), typeof(DockPanelWithOverlay), new PropertyMetadata(Visibility.Visible));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public UIElementCollection Children
{
get { return (UIElementCollection)GetValue(ChildrenProperty); }
set { SetValue(ChildrenProperty, value); }
}
public static readonly DependencyProperty ChildrenProperty =
DependencyProperty.Register("Children", typeof(UIElementCollection), typeof(DockPanelWithOverlay), new PropertyMetadata(null));
}
Using the DockPanelWithOverlay :
<lib:DockPanelWithOverlay x:Name="dockPanelWithOverlay1"
OverlayVisibility="Visible"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Button Content="Top" Height="50" DockPanel.Dock="Top" Background="Red"/>
<Button Content="Bottom" Height="50" DockPanel.Dock="Bottom" Background="Yellow"/>
<Button Content="Left" Width="50" DockPanel.Dock="Left" Background="Pink"/>
<Button Content="Right" Width="50" DockPanel.Dock="Right" Background="Bisque"/>
<Button Content="Center" Background="Azure"/>
<lib:DockPanelWithOverlay.Overlay>
<Grid Background="#80404080">
<TextBlock Text="Overlay" FontSize="80" Foreground="#FF444444" HorizontalAlignment="Center" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-15"/>
<TranslateTransform/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Grid>
</lib:DockPanelWithOverlay.Overlay>
</lib:DockPanelWithOverlay>
The overlay can easily be switched on or off binding from a CheckBox.IsChecked property for instance.
Here is the full working code : http://1drv.ms/1NfCl9z
I think it 's really the answer to your question. Regards
I suggest we should try to clarify how you see this working. My guess is that the secondary panel will be a DockPanel too, and will completely cover the main panel. I.e., you see either the one or the other, but never both. How do you envisage switching between the two? A ToggleButton perhaps? Or only under control of, say, some Trigger?
My first thought as to implementation is that you seem to like how DockPanel? lays things out, so why touch the layout methods? One way might be to have only one dockpanel, but two collections of children, which you set according to which you want to show. Or the secondary panel in a `Popup?
Do you want to be able to write something like this:
<DockPanelWithAlternative
AlternativeVisibility="{Binding somethingHere}" >
<TextBlock Dock.Top ... />
<TextBlock Dock.Alternative.Top ... />
</DockPanelWithAlternative>
What I am thinking of is something like:
<UserControl>
<Grid>
<DockPanel x:Name="MainPanel" ZIndex="0"/>
<DockPanel x:Name="AlternativePanel" Visbility=... ZIndex="1"/>
</Grid>
</UserControl>

Items of ItemsPresenter in Custom-Control not shown correctly

I've written a custom control that should display items in a list and provide additional commands related to scrolling events, like load more. So I decided to create a ScrollViewer and add the ItemsPresenter inside generic.xaml.
Basically this works fine when I use ItemsControl as base class. But now I need the possibility to click on a single item. The solution is to use the ListView class as base class.
Here comes the problem. As soon as I use a GridView or ListView as base class the content of the list is only shown as far as it is shown at inital offset of the list. other/new items that you can only see by scrolling down aren't shown.
I thought that the list maybe dont resize, but adding a footer and a border around the list shows the the list resizes correctly.
The collection I use is a ObservableCollection and works with the ItemsControl base class.
I think the problem is a setting or something on xaml side. But I dont know where and i have no ideas what to search next. everytime I search, all results are marked as clicked =(
Here is my xaml code of the generic.xaml:
<Style TargetType="lists:ListViewWithCommands">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="lists:ListViewWithCommands">
<Border
Background="{TemplateBinding Background}"
BorderBrush="Red"
BorderThickness="1">
<ScrollViewer x:Name="ItemScrollViewer">
<Border BorderBrush="White" BorderThickness="2">
<StackPanel>
<ItemsPresenter />
<ContentPresenter Visibility="{TemplateBinding LoadingTemplateVisibility}" ContentTemplate="{TemplateBinding LoadingTemplate}" />
</StackPanel>
</Border>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here is a part of my control itselve:
[TemplatePart(Name="ItemScrollViewer", Type=typeof(ScrollViewer))]
public sealed class ListViewWithCommands : ListView
{
private ScrollViewer _itemScrollViewer;
public ListViewWithCommands()
{
this.DefaultStyleKey = typeof(ListViewWithCommands);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
this._itemScrollViewer = GetTemplateChild("ItemScrollViewer") as ScrollViewer;
if (this._itemScrollViewer != null)
{
Debug.WriteLine(String.Format("ItemScrollViewer found! Attatching Event Handler!"), this.GetType().Name);
this._itemScrollViewer.ViewChanged += _itemScrollViewer_ViewChanged;
}
else
{
throw new NullReferenceException("ItemScrollViewer not found!");
}
}
....
I hope you have any suggestions.
robert

Accessing elements in a xaml defined style

I have followed the DiagramDesigner example on Codeproject for learning how to use Adorners in WPF as it fits quite a few of my needs relatively closely.
I have adapted the implementation a little, and also added my own adorner, for controlling the opacity of a control via a slider (slider on the adorner).
Following the same methods as the author, I placed the slider and other feature in a xaml style definition file as below. I am just now struggling A) to figure out how to access the slider at any level, B) how best to start hooking this up with an underlying Viewmodel that will be used for various settings (on adorners).
<Style x:Key="OpacityAdorner" TargetType="{x:Type adorners:OpacityChrome}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type adorners:OpacityChrome}">
<Grid>
<Slider x:Name="OpacitySlider" Style="{StaticResource OpacityControl}" ToolTip="Alter the opacity of the image to overlay with other images" Visibility="Collapsed"/>
<Ellipse x:Name="OpacitySliderEnable" Style="{StaticResource OpacityIcon}" ToolTip="Alter the visual opacity of the image" Visibility="Visible"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The codeproject example is here http://www.codeproject.com/Articles/22952/WPF-Diagram-Designer-Part
A) Use something like the following snippet to get the slider from the applied template.
var slider = opacityAdorner.Template.FindName("OpacitySlider", opacityAdorner) as Slider;
there are cases where the template has not yet been applied, in that case you need to preceed the previous call with the following:
opacityAdorner.ApplyTemplate();
B) The best approach for hooking up with the view model (in my opinion) is to expose the required properties as dependency properties on the OpacityChrome adorner. You then use normal Binding to hook up the new properties to the view-model, and TemplateBinding to hook them up to the template elements.

Reference to control inside a ControlTemplate

How do I, form my contructor in the code-behind get a reference to the OuterBorder control in the XAML below?
<Window Template="{DynamicResource WindowTemplate}">
<Window.Resources>
<ControlTemplate x:Key="WindowTemplate" TargetType="{x:Type Window}">
<AdornerDecorator>
<Border Name="OuterBorder" Background="Black" BorderBrush="Red" BorderThickness="1" CornerRadius="0">
<!-- Implementation here... -->
</Border>
</AdornerDecorator>
</ControlTemplate>
</Window.Resources>
</Window>
Two possible solutions:
Solution 1
Put a Loaded event in XAML
<Border Name="OuterBorder" Loaded="Border_Loaded" ...
And in code behind store it in a private field:
private Border border;
void Border_Loaded(object sender, RoutedEventArgs e)
{
this.border = (Border)sender;
}
OR:
Solution 2
Override the OnApplyTemplate of your Window:
private Border border;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.border = (Border) Template.FindName("OuterBorder", this);
}
You may want to reconsider your approach. What are you trying to do?
Generally, you shouldn't want or need to access portions of the ControlTemplate from your codebehind because your template is just that-- a template. It's how the control looks. You want your codebehind to generally affect the behavior of the control.
For example, if you're trying to affect the color of the border in the codebehind in certain interactive situations, you really want to add some (pre .Net4) triggers or (post .Net4) a VisualStateManager to your control template to manage your control's visual states for you.

Categories

Resources