Sending UIElements via DependencyProperty in wpf - wpf

I have a user control with a DependencyProperty that takes a UIElement. So far, so good, the problem is I cannot find the element's children.
I think the problem is my lack of knowledge, could anyone tell me what the problem is and a possible solution?
I have made a small test-program like this
Usercontrol codebehind:
public UIElement TestSendUiElement
{
get { return (StackPanel)GetValue(TestSendUiElementProperty); }
set { SetValue(TestSendUiElementProperty, value); }
}
public static readonly DependencyProperty TestSendUiElementProperty =
DependencyProperty.Register("TestSendUiElement", typeof(StackPanel), typeof(Test), new FrameworkPropertyMetadata(TestSendUiElementPropertyChanged));
private static void TestSendUiElementPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine(VisualTreeHelper.GetChildrenCount((UIElement)e.NewValue));
}
xaml using the usercontrol:
<my:Test >
<my:Test.TestSendUiElement>
<StackPanel Orientation="Horizontal" Margin="0,2">
<TextBox Height="23" Width="50" Margin="0,0,5,0" />
<TextBox Height="23" Width="125" />
</StackPanel>
</my:Test.TestSendUiElement>
</my:Test>
Output is 0 children. Shouldn't it be 2?

The content is no Initialized so count the object on initialization
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
Console.WriteLine(VisualTreeHelper.GetChildrenCount((UIElement)e.NewValue));
}
And you will get count 2

I think it doesn't work because whatever you assign to the TestSendUiElement DependencyProperty, it won't be part of the VisualTree. So VisualTreeHelper.GetChildrenCount(...) will not work.
As a direct replacement, LogicalTreeHelper should do the trick.
And if you know the type of the object or can , then it's even better to use exposed properties like ItemsControl.Items, ContentControl.Content and etc., with the exception of classes inheriting from Panel (they're LogicalChildren property is internal).
If you are lazy you could also do the following (untested code):
<my:Test.TestSendUiElement>
<ItemsControl Margin="0,2">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<TextBox Height="23" Width="50" Margin="0,0,5,0" />
<TextBox Height="23" Width="125" />
<ItemsControl>
</my:Test.TestSendUiElement>
Then you change the type of the DP property to ItemsControl, and now you can access the children via this.TestSendUIElement.Items. An ItemsControl is probably not as lightweight as a panel, but using the LogicalTreeHelper probably wouldn't be optimal either. Depends on the scenario.

Related

Binding disappears on simple user control

I have a simple usercontrol with a single DependencyProperty. I am unable to set bindings on this property. I don't get any exceptions but the bindings just disappear.
I cannot begin to see what is going wrong here. It's so simple.
Here's my usercontrol:
XAML:
<UserControl x:Class="Test.Controls.SimpleUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="ucThis">
<TextBox Text="{Binding Path=MyString, ElementName=ucThis}" />
</UserControl>
Code:
public partial class SimpleUserControl : UserControl
{
public SimpleUserControl()
{
InitializeComponent();
}
public string MyString
{
get { return (string)GetValue(MyStringProperty); }
set { SetValue(MyStringProperty, value); }
}
public static readonly DependencyProperty MyStringProperty =
DependencyProperty.Register("MyString", typeof(string),
typeof(SimpleUserControl), new UIPropertyMetadata("simple user control"));
}
XAML from a test app:
<StackPanel>
<testControls:SimpleUserControl MyString="{Binding Path=TestString}"
x:Name="simpleUC" />
<Label Content="From control" />
<Border Margin="5"
BorderBrush="Black"
BorderThickness="1"
Visibility="{Binding Path=MyString, ElementName=simpleUC, Converter={StaticResource nullVisConv}}">
<ContentPresenter Content="{Binding Path=MyString, ElementName=simpleUC}" />
</Border>
<TextBlock Text="Value from control is null."
Margin="5"
Visibility="{Binding Path=MyString, ElementName=simpleUC, Converter={StaticResource nullVisConv}, ConverterParameter={custom:BooleanValue Value=True}}" />
<Label Content="From binding" />
<Border Margin="5"
BorderBrush="Black"
BorderThickness="1"
Visibility="{Binding Path=TestString, Converter={StaticResource nullVisConv}}">
<ContentPresenter Content="{Binding Path=TestString}" />
</Border>
<TextBlock Text="Value from binding is null."
Margin="5"
Visibility="{Binding Path=TestString, Converter={StaticResource nullVisConv}, ConverterParameter={custom:BooleanValue Value=True}}" />
<TextBox Text="You can set focus here." />
</StackPanel>
The main window for the test app has a property named TestString, is its own DataContext and implements INotifyPropertyChanged correctly. SimpleUserControl.MyString updates as it should but the property it is bound to (TestString) does not. I have inspected this with Snoop; the binding I set on the SimplerUserControl is just not present at run time. What is happening here?
UPDATE
Okay. So if I specify Mode=TwoWay the binding works. That's great. Can anyone explain to me why it behaves this way?
Thanks.
Working as designed :). DPs default to 1-way. Personally, I would change your DependencyProperty.Register() call to make the DP default to two-way. That way you don't have to specify two-way explicitly every time you use it. You'll notice that the framework typically makes DPs two-way by default when you'd want the property to write back. Just a convienience.
Yes you need to provide TwoWay Mode for Dependency Property:
public string MyString
{
get { return (string)GetValue(MyStringProperty); }
set { SetValue(MyStringProperty, value); }
}
public static readonly DependencyProperty MyStringProperty =
DependencyProperty.Register("MyString", typeof(string),
typeof(UserControl1), new FrameworkPropertyMetadata("simple user control", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

wpf DependencyProperty for button custom control

I have been making my own custom control and I ran into trouble when I wanted to my OwnButton to react as proper component. I would like my component's content be "ownButton" when it is dragged to designer and then there would be a property "Content" in property-list where a programmer would be able to change that text to whatever is needed.
Here is a part of my Generic.xaml file:
<Button x:Name="PART_Button" Grid.Row="1" Margin="1,1,1,1" Template="{StaticResource OwnButtonTemplate}">
<TextBlock Text="ownButton" FontFamily="Tahoma" FontSize="10" HorizontalAlignment="Center" VerticalAlignment="Center" Name="textBlock" />
</Button>
I was able to make a DependencyProperty in OwnButton class, but it isn't linked to Button's content. Here is the code:
private const string defaultContent = "ownButton";
public String Content
{
get { return (String)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(String), typeof(OwnButton), new FrameworkPropertyMetadata(OwnButton.defaultContent, new PropertyChangedCallback(OwnButton.OnContentChanged), new CoerceValueCallback(OwnButton.CoerceContent)));
Would someone be able to help me with my problem?
It is not possible to bind to component's own DependencyProperty. However, finally I found a solution when I used the fact that the component is a Button with a TextBlock in it. My solution doesn't work on the designer as you can guess from the code, but does what it should do when the program is executed.
<Button x:Name="PART_Button" Grid.Row="1" Margin="1,1,1,1" Template="{StaticResource ButtonRandomTemplate}">
<TextBlock Text="{Binding Content, ElementName=PART_Button, UpdateSourceTrigger=PropertyChanged}" FontFamily="Tahoma" FontSize="10" HorizontalAlignment="Center" VerticalAlignment="Center" Name="textBlock" />
I used databinding to the TextBlock's Text-property and now I was able to combine my component's DependencyProperty Content and PART_Button in code.

Silverlight 4 MVVM: Unable to Databind ICommand

I have created a Silverlight User Control. The markup is:
<StackPanel Grid.Row="4" Grid.Column="0" Orientation="Horizontal" Width="Auto" Margin="5">
<Button Content="OK" Margin="0,0,5,5" MinWidth="50" Command="{Binding OKCommand}" />
</StackPanel>
The code behind declares a Dependency property 'OKCommand' as:
public ICommand OKCommand
{
get
{
return (ICommand)GetValue(OKCommandProperty);
}
set
{
SetValue(OKCommandProperty, value);
}
}
public static readonly DependencyProperty OKCommandProperty
= DependencyProperty.Register("OKCommand", typeof(ICommand), typeof(TestUserControl), new PropertyMetadata(null, OKCommandProperty_PropertyChangedCallback));
private static void OKCommandProperty_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
Now I want to use the user control on another page where which is the View & the ViewModel defines the command to which I want the OKCommand to be bound. The XAML markup is as such:
<local:TestControl OKCommand="{Binding Path=TestControlOk}"/>
However when I click the button it does not execute anything. Any clues as to what I am doing wrong here.
You need to show the view model that contains the TestControlOk property so we can tell if that's part of the problem.
UserControls do not register themselves as the data context automatically so the binding inside the user control won't have anything to bind to. Do you have
this.DataContext = this;
anywhere in the UserControl codebehind to enable your first binding to actually work?
Alternatively, you can do something like so:
<UserControl .....
x:Name="MyUserControl">
<StackPanel Grid.Row="4" Grid.Column="0" Orientation="Horizontal" Width="Auto" Margin="5">
<Button Content="OK" Margin="0,0,5,5" MinWidth="50"
Command="{Binding OKCommand, ElementName=MyUserControl}" />
</StackPanel>
</UserControl>
Note the ElementName= part of the binding pointing to the root UserControl element in your XAML.

Silverlight: Define event handler in hierarchical data template

I am having problems getting at a click event of a button and am using Silverlight 3.0 w/ matching Silverlight Toolkit.
Problem
I have this TreeView:
TreeView sample http://a.imagehost.org/0939/TreeView.png.
The value for a certain node is the sum of the values of its children. Only in leaves can data be added (for the time being).
What I want to achieve is that a user can add (and eventually remove) entries in the tree to eventually create a custom diagram.
To that end I would like the "plus sign" to insert a new line / node as child of the node where the user clicked. (I.e. if I click the plus at "Display", I get a line below to specify CRT or TFT or whatever.)
Thing is, for all my brain is worth, I don't know how to receive any useful event.
The TextBlock, TextBox and Button are defined in a hierarchical template and I can't define a Click-handler in that template.
OTOH, I haven't found a way to get at the template items of a certain TreeViewItem from within (c#) code. Very well am I able to do trv.ItemContainerGenerator.GetContainerFromItem(item), and as Justin Angel showed I can very well change the font size, but didn't find any way to access the textbox or button.
Is there any way to capture the click event to the button? Or any alternative way of getting something that gives the "add below" functionality?
Thank you in advance.
More Data
The treeview xaml is this:
<controls:TreeView x:Name="SankeyDataTree"
ItemTemplate="{StaticResource SankeyTreeTemplate}" BorderThickness="0"
Background="{x:Null}" HorizontalAlignment="Left" VerticalAlignment="Top">
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.HeaderTemplate>
<DataTemplate>
<TextBlock Text="Loading..."/>
</DataTemplate>
</controls:TreeViewItem.HeaderTemplate>
</controls:TreeViewItem>
</controls:TreeView>
I use this HierarchicalDataTemplate (and stole the appraoch from Timmy Kokke):
<Data:HierarchicalDataTemplate x:Key="SankeyTreeTemplate" ItemsSource="{Binding Children}">
<Grid Height="24">
<Grid.ColumnDefinitions>
<!-- ... -->
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Value.name, Mode=TwoWay}" VerticalAlignment="Center"/>
<TextBox Text="{Binding Path=Value.flow, Mode=TwoWay}" Margin="4,0" VerticalAlignment="Center" d:LayoutOverrides="Width" Grid.Column="1" TextAlignment="Right" Visibility="{Binding Children, Converter={StaticResource BoxConverter}, ConverterParameter=\{box\}}"/>
<TextBlock Text="{Binding Path=Value.throughput, Mode=TwoWay}" Margin="4,0" VerticalAlignment="Center" d:LayoutOverrides="Width" Grid.Column="1" TextAlignment="Right" Visibility="{Binding Children, Converter={StaticResource BoxConverter}, ConverterParameter=\{block\}}"/>
<Button Margin="0" Grid.Column="2" Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</Grid>
</Data:HierarchicalDataTemplate>
To this TreeView is bound a "SimpleTree", whose Values hold basically onle a string (name) and two doubles (flow and throughput).
public String name { get; set; }
public Double flow { get; set; }
public Double throughput { get; set; }
(Plus the code for the INotifyPropertyChanged to get a twoway bind to the text boxes.)
You can attach a Behavior to the Button in the HierarchicalDataTemplate and let that handle Click events from the Button.
Download and install the Expression Blend 3 SDK. Add a reference to System.Windows.Interactivity in the project and add a Behavior attached to a Button:
public class ButtonClickBehavior : Behavior<Button> {
protected override void OnAttached() {
base.OnAttached();
AssociatedObject.Click += ButtonClick;
}
protected override void OnDetaching() {
base.OnDetaching();
AssociatedObject.Click -= ButtonClick;
}
void ButtonClick(object sender, RoutedEventArgs e) {
Node node = AssociatedObject.DataContext as Node;
if (node != null) {
// Button clicked. Do something to associated node.
}
}
}
Attach the Behavior to the Button in the HierarchicalDataTemplate (assuming this namespace declaration: xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"):
<Button Margin="0" Grid.Column="2" Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<interactivity:Interaction.Behaviors>
<local:ButtonClickBehavior/>
</interactivity:Interaction.Behaviors>
</Button>
If desired you can add properties to ButtonClickBehavior and set those from XAML to create a more reusable Behavior.
You can handle the button click event in the code behind. To get to the data, just bind it to the Tag attribute.
<Button Margin="0" Grid.Column="2"
Click="Button_Click" Tag="{Binding}"
Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
In the code behind, handle it and access the element.
private void Button_Click(object sender, RoutedEventArgs e)
{
var data = ((Button)sender).Tag as SimpleTreeNode
}
Where SimpleTreeNode is the name of your tree element class.
You should be able to add a new node to the data found now.

Looking for a WPF ComboBox with checkboxes

My google skills fail me. Anyone heard of a control like that for WPF. I am trying to make it look like this (winforms screenshot).
You can do this yourself by setting the DataTemplate of the combo box. This article shows you how - for a listbox, but the principle is the same.
Another article here is perhaps a better fit for what you are trying to do, simple set the first column of the item template to be a checkbox and bind it to a bool on your business object.
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}"
Width="20" />
<TextBlock Text="{Binding DayOfWeek}"
Width="100" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
There is my combobox. I use Martin Harris code and code from this link Can a WPF ComboBox display alternative text when its selection is null?
<ComboBox Name="cbObjects" Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" Margin="2,2,6,0" SelectionChanged="OnCbObjectsSelectionChanged" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}" Width="20" VerticalAlignment="Center" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
<TextBlock Text="{Binding ObjectData}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock IsHitTestVisible="False" Name="tbObjects" Text="Выберите объекты..." Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" Margin="6,2,6,0" />
Small class for datasource:
public class SelectableObject <T> {
public bool IsSelected { get; set; }
public T ObjectData { get; set; }
public SelectableObject(T objectData) {
ObjectData = objectData;
}
public SelectableObject(T objectData, bool isSelected) {
IsSelected = isSelected;
ObjectData = objectData;
}
}
And there is two handler - one for handling CheckBox clicked and one for forming Text for ComboBox.
private void OnCbObjectCheckBoxChecked(object sender, RoutedEventArgs e) {
StringBuilder sb = new StringBuilder();
foreach (SelectableObject<tblObject> cbObject in cbObjects.Items)
{
if (cbObject.IsSelected)
sb.AppendFormat("{0}, ", cbObject.ObjectData.Description);
}
tbObjects.Text = sb.ToString().Trim().TrimEnd(',');
}
private void OnCbObjectsSelectionChanged(object sender, SelectionChangedEventArgs e) {
ComboBox comboBox = (ComboBox)sender;
comboBox.SelectedItem = null;
}
For ComboBox.ItemsSource I use
ObservableCollection<SelectableObject<tblObject>>
where tblObject is type of my object, a list of which I want to display in ComboBox.
I hope this code is useful to someone!
Give a try to CheckComboBox from Extended WPF Toolkit.
The main advantage for me is having two lists for binding:
all items available for selection
just selected items
I find this approach more practical. In addition you can specify value and display members of the collections you're binding.
If you don't want to bring a bunch of other controls with CheckComboBox, you can get the source code of it, it's pretty straightforward (need to bring Selector class as well).
ComboBox with Checkboxes
<ComboBox Height="16" Width="15">
<CheckBox Content="First Checkbox" />
<CheckBox Content="Second Checkbox" />
<CheckBox Content="Third Checkbox" />
<TextBlock Text="Some Text" />
</ComboBox>
The provided answers surprisingly didn't work for me, I tried many variations and kept getting error messages about the checkbox not being part of combobox and the data context seemed to be broken.
In the end I didn't have to do anything involving data templates or any code behind and my bindings are working fine (not shown in example)
I must say I'm happy with how easy this turned out to be after reading all the answers.

Resources