WPF Binding: How do I reference element outside a user control? - wpf

My main window has following portion of XAML
<StackPanel>
<TextBox x:Name="textbox1" />
<my:UserControl1 />
</StackPanel>
my:UserControl1 goes like this
<TextBox x:Name="userControlTextBox" />
I would like to bind the above textbox's Text property to Text property of the textbox outside.
I have tried
<TextBox x:Name="userControlTextBox" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=textbox1.Text}" />

You can't bind like you shown in the question. There are two ways to achieve that:
First, bind Text of textBox1 to some property in ViewModel. And bind Text of userControlTextBox to same bound property.
<TextBox x:Name="textbox1" Text="{Binding SomeProperty}"/>
and in UserControl
<TextBox x:Name="userControlTextBox" Text="{Binding SomeProperty}"/>
Make sure your property is notifiable (INPC should be implemented by containing class) so that any changes in it gets propagated to GUI.
Second would be to create custom DP on your userControl of type string. Bind that DP with Text value of textbox1. And bind Text of userControlTextBox to that DP.
<StackPanel>
<TextBox x:Name="textbox1" />
<my:UserControl1 TextValue="{Binding Text, ElementName=textBox1}"/>
</StackPanel>
Here, TextValue is custom DP declared in your userControl.
public string CustomValue
{
get { return (string)GetValue(CustomValueProperty); }
set { SetValue(CustomValueProperty, value); }
}
public static readonly DependencyProperty CustomValueProperty =
DependencyProperty.Register("CustomValue", typeof(string),
typeof(UserControl1));
Now, bind textBox in UserControl like this:
<TextBox x:Name="userControlTextBox"
Text="{Binding TextValue, RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl}}"/>

Related

MVVM WPF Combobox: creating a template for comboboxitem

I have a WPF combobox which is populated from code-behind.
Code-behind (xaml.cs):
namespace WpfApplication1
{
private ObservableCollection<TransportType> transportTypes = new ObservableCollection<TransportType>();
transportTypes.Add(new TransportType() {Icon = Properties.Resources.Air, ValueMember = "A100", DisplayMember = "By Air" });
transportTypes.Add(new TransportType() {Icon = Properties.Resources.Maritime, ValueMember = "M200", DisplayMember = "Maritime" });
this.ComboBoxTransportTypes.ItemsSource = transportTypes;
}
TransportType class:
namespace WpfApplication1
{
public class TransportType
{
public Image Icon
{
get;
set;
}
public string DisplayMember
{
get;
set;
}
public string ValueMember
{
get;
set;
}
}
}
View:
<ComboBox x:Name="ComboBoxTransportTypes"
Grid.Column="1"
ItemsSource="{Binding}"
DisplayMemberPath="DisplayMember"
SelectedValuePath="ValueMember"
SelectionChanged="ComboBoxTransportTypes_SelectionChanged">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
Now I am trying to apply a ComboBox ItemTemplate and bound to the "transportTypes" collection. I would like each combobox item to be as below:
<ComboBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding bind-icon-here}" />
<TextBlock Foreground="AliceBlue"
VerticalAlignment="Center"
Text="{Binding bind-DisplayMember-here}"/>
</StackPanel>
</ComboBoxItem>
So how can I create the above combobox item template bound to my collection in order to each item to be presented with an icon followed by a string?
I have tried below but it does not work. I also do not know how to bind each item in the collection to the image and textblock within stackpanel, I have done as below but only string is displayed and not icon.
<ComboBox x:Name="ComboBoxTransportTypes"
Grid.Column="1"
ItemsSource="{Binding}"
DisplayMemberPath="DisplayMember" <-- removed from here as I cannot define DisplayMemberPath and item template at the same time.
SelectedValuePath="ValueMember"
SelectionChanged="ComboBoxTransportTypes_SelectionChanged">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate DataType="l:TransportType">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Icon}" />
<TextBlock Foreground="AliceBlue"
VerticalAlignment="Center"
Text="{Binding DisplayMember}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Also, in MVVM is it better to populate combobox from code-behind as I have done here or from view model constructor?
The problem is that you're setting the ItemSource in both the XAML and in the code behind. If you remove ItemSource="{Binding}" from the XAML then it should work.
If you are using MVVM, the collection should be populated in the view model, not in the code behind. There should be very little code in your code behind - only things related to the view should go there (such as displaying a child window).
The problem is that Image.Source takes an ImageSource not an Image. Change ...
<Image Source="{Binding Icon}" />
to...
<Frame Content="{Binding Icon}"/>
and things will start working.
you must use bottom syntax for custom template ComboBox
<TextBlock Text="{Binding Path=DisplayMember}"/>
or
<Image Source="{Binding Path=Icon}" />
in wpf list tags like combobox,listbox,... for custom tamplate like DataTemplate you must use Path

How to set textblock to checkbox content

Not getting a proper solution on how add a textblock to checkbox content property in XAML.
Issue is that checkbox content is not properly displayed for other languages.
So i need to add a textblock to it and apply texttrimming with "CharacterEllipsis".
Please help with a sample XAML code
I have the other properties defined for our custom checkbox:
<uxCCl:UxCheckBox x:Name="btnShowHide"
Height="24"
MaxWidth="200"
Margin="16,0,0,0"
HorizontalAlignment="Left"
Command="{Binding Path=ShowHideErrorOutCommand}"
Foreground="#638EAC"/>
So simply doing this won't work:
<Checkbox.Content>
<Textblock> <Text></Textblock>
</Checkbox.Content>
Please suggest some solution.
Simply add the TextBlock to the CheckBox content like so:
<CheckBox>
<CheckBox.Content>
<TextBlock Text="text block" TextTrimming="CharacterEllipsis"></TextBlock>
</CheckBox.Content>
</CheckBox>
EDIT:
Edit the custom CheckBox like I suggested above.
Add a Dependency Property to the custom CheckBox:
public string TextContent
{
get { return GetValue(TextContentProperty).ToString(); }
set { SetValue(TextContentProperty, value); }
}
public static readonly DependencyProperty TextContentProperty = DependencyProperty.Register(
"TextContent",
typeof(string),
typeof(YOURCLASSNAME),
new PropertyMetadata(string.Empty)
);
Add a Binding to the Text property of the TextBlock:
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=TextContent}"></TextBlock>
<CheckBox.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</CheckBox.ContentTemplate>

WPF: relativesource data binding on a custom dependencyproperty

I'm trying to create a custom multi value combobox. So basically a combobox with some checkboxes as items. The idea is, to keep the whole control fully bindable, so that I can be reused any time.
Here's the XAML
<ComboBox x:Class="WpfExtensions.Controls.MultiSelectComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfExtensions.Controls"
mc:Ignorable="d" d:DesignHeight="23" d:DesignWidth="150">
<ComboBox.Resources>
<local:CheckBoxConverter x:Key="CheckBoxConverter" />
</ComboBox.Resources>
<ComboBox.ItemTemplateSelector>
<local:MultiSelectBoxTemplateSelector>
<local:MultiSelectBoxTemplateSelector.SelectedItemsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MultiSelectComboBox}}, Path=SelectedItems, Converter={StaticResource CheckBoxConverter}}" />
</DataTemplate>
</local:MultiSelectBoxTemplateSelector.SelectedItemsTemplate>
<local:MultiSelectBoxTemplateSelector.MultiSelectItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" HorizontalAlignment="Stretch"
Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" Indeterminate="CheckBox_Checked" Click="CheckBox_Checked" />
</DataTemplate>
</local:MultiSelectBoxTemplateSelector.MultiSelectItemTemplate>
</local:MultiSelectBoxTemplateSelector>
</ComboBox.ItemTemplateSelector>
</ComboBox>
And the code behind for the custom property "SelectedItems"
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectComboBox));
[Bindable(true)]
public IList SelectedItems
{
get
{
return (IList)GetValue(SelectedItemsProperty);
}
private set
{
SetValue(SelectedItemsProperty, value);
}
}
Now when I test the project, the RelativeSource is resolved correctly towards the control itself, however the Binding on the path "SelectedItems" fails with the debugger stating, that there is no such Path on the RelativeSource object.
Did I mess up the binding or did I make a complete logical error?
You are setting a RelativeSource as the Source, instead set the RelativeSource propperty like so:
<TextBlock Text="{Binding Path=SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type local:MultiSelectComboBox}}, Converter={StaticResource CheckBoxConverter}}" />

Is it possible to bind code-behind property without setting DataContext?

As titled,
I seen couples of similiar question this or this in SO, but I don't see a solution for it.
I know if I need to bind to the code-beind, I need to set Datacontext = this
But my problem is that my datacontext already binding to my ViewModel, but I want to do some UI manipulation with using Command which is defined in the code-beind.
Is it possbile to bind it in xaml? If so, how?
EDIT: I did tried the follows:
<Window x:Class="WpfApplication3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" x:Name="_Root">
<Grid x:Name="hellogrid">
<TextBlock x:Name="myTextBlock" Text="AAAA"/>
<Button Margin="82,119,121,120" Name="button2" Content="{Binding Path=Text, ElementName=myTextBlock}"/>
<Button Margin="82,72,121,0" Name="button3" Content="{Binding Path=MyText, ElementName=_Root}" Height="23" VerticalAlignment="Top" />
</Grid>
And code-behind:
public partial class Window1 : Window
{
public string MyText { get; set; }
public Window1()
{
InitializeComponent();
MyText = "ABC";
}
}
I could see the Button2 shows AAAA, but Button3 shows nothing....
Of course
There are many types of bindings. The most basic one binds to a property on the DataContext, which is usually inherited from a Parent object
<DataTemplate DataType="{x:Type MyModel}">
<!-- DataContext is object of type MyModel -->
<local:MyView />
</DataTemplate>
Or
<Window x:Name="MyWindow">
<!-- DataContext Inherited from Window -->
<TextBlock Text="{Binding SomeProperty}" />
</Window>
where
var SomeObject = new SomeModel();
SomeObject.SomeProperty = "Test";
myWindow.DataContext = SomeObject;
Other binding types include ElementName, where you can specify the target UI element to use as the data source for the binding
<StackPanel>
<CheckBox x:Name="SomeCheckBox" />
<TextBlock Text="{Binding ElementName=SomeCheckBox, Path=IsChecked}" />
</StackPanel>
or
<local:MyUserControl x:Name="SomeUserControl">
<Button Command="{Binding ElementName=SomeUserControl, Path=DataContext.SaveCommand}" />
</local:MyUserControl >
Or RelativeSource, which allows you to find an object relative to the current object to use as a DataSource
<Window Title="Test">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=Title}" />
</Window>
or
<local:MyUserControl>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MyUserControl}}, Path=DataContext.SaveCommand}" />
</local:MyUserControl >
And TemplateBinding, which binds is a shortcut to a RelativeSource binding that binds to a templated object
<Button Content="Test">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<TextBlock Text="{TemplateBinding Content}" />
</ControlTemplate>
</Button.Template>
</Button>
EDIT
The best solution IMO is the one posted by #Saad Imran in this SO question...
With this solution all you have to do is name your window and binding to a property in your XAML will be as easy as this {Binding ElementName=MyWindowName, Path=MyText}
So, what you are doing with Content="{Binding Path=MyText, ElementName=_Root}" is exactly right and your Button Content property IS bound to MyText property but the only thing you are missing is change notification (need to implement INotifyPropertyChanged interface for that) so when you set your MyText property to ABC MyText = "ABC"; no change notification is sent...
Easy way to test this is by setting the MyText property explicitly as such:
private string myText = "ABC";
public string MyText
{
get { return myText; }
set { myText = value; }
}
or setting it in the constructor before InitializeComponent() is called:
MyText = "ABC";
InitializeComponent();
If you do that you'll notice that your button will have ABC as its content but changes to MyText property will not affect the button content because there is no change notification...
Sure, you can use ElementName:
<Window Name="root"
Class="..."
...>
...
<TextBox Text="{Binding Path=Foo, ElementName=root}" />
You could also do it with RelativeSource, but the syntax is uglier...

WPF binding to Listbox selectedItem

Can anyone help with the following - been playing about with this but can't for the life of me get it to work.
I've got a view model which contains the following properties;
public ObservableCollection<Rule> Rules { get; set; }
public Rule SelectedRule { get; set; }
In my XAML I've got;
<ListBox x:Name="lbRules" ItemsSource="{Binding Path=Rules}"
SelectedItem="{Binding Path=SelectedRule, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox x:Name="ruleName">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
</TextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Now the ItemsSource works fine and I get a list of Rule objects with their names displayed in lbRules.
Trouble I am having is binding the SelectedRule property to lbRules' SelectedItem. I tried binding a textblock's text property to SelectedRule but it is always null.
<TextBlock Text="{Binding Path=SelectedRule.Name}" />
The error I'm seeing in the output window is:
BindingExpression path error: 'SelectedRule' property not found.
Can anyone help me with this binding - I can't see why it shouldn't find the SelectedRule property.
I then tried changing the textblock's text property as bellow, which works. Trouble is I want to use the SelectedRule in my ViewModel.
<TextBlock Text="{Binding ElementName=lbRules, Path=SelectedItem.Name}" />
Thanks very much for your help.
First off, you need to implement INotifyPropertyChanged interface in your view model and raise the PropertyChanged event in the setter of the Rule property. Otherwise no control that binds to the SelectedRule property will "know" when it has been changed.
Then, your XAML
<TextBlock Text="{Binding Path=SelectedRule.Name}" />
is perfectly valid if this TextBlock is outside the ListBox's ItemTemplate and has the same DataContext as the ListBox.
Inside the DataTemplate you're working in the context of a Rule, that's why you cannot bind to SelectedRule.Name -- there is no such property on a Rule.
To bind to the original data context (which is your ViewModel) you can write:
<TextBlock Text="{Binding ElementName=lbRules, Path=DataContext.SelectedRule.Name}" />
UPDATE: regarding the SelectedItem property binding, it looks perfectly valid, I tried the same on my machine and it works fine. Here is my full test app:
XAML:
<Window x:Class="TestWpfApplication.ListBoxSelectedItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxSelectedItem" Height="300" Width="300"
xmlns:app="clr-namespace:TestWpfApplication">
<Window.DataContext>
<app:ListBoxSelectedItemViewModel/>
</Window.DataContext>
<ListBox ItemsSource="{Binding Path=Rules}" SelectedItem="{Binding Path=SelectedRule, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
Code behind:
namespace TestWpfApplication
{
/// <summary>
/// Interaction logic for ListBoxSelectedItem.xaml
/// </summary>
public partial class ListBoxSelectedItem : Window
{
public ListBoxSelectedItem()
{
InitializeComponent();
}
}
public class Rule
{
public string Name { get; set; }
}
public class ListBoxSelectedItemViewModel
{
public ListBoxSelectedItemViewModel()
{
Rules = new ObservableCollection<Rule>()
{
new Rule() { Name = "Rule 1"},
new Rule() { Name = "Rule 2"},
new Rule() { Name = "Rule 3"},
};
}
public ObservableCollection<Rule> Rules { get; private set; }
private Rule selectedRule;
public Rule SelectedRule
{
get { return selectedRule; }
set
{
selectedRule = value;
}
}
}
}
Yocoder is right,
Inside the DataTemplate, your DataContext is set to the Rule its currently handling..
To access the parents DataContext, you can also consider using a RelativeSource in your binding:
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ____Your Parent control here___ }}, Path=DataContext.SelectedRule.Name}" />
More info on RelativeSource can be found here:
http://msdn.microsoft.com/en-us/library/system.windows.data.relativesource.aspx
For me, I usually use DataContext together in order to bind two-depth property such as this question.
<TextBlock DataContext="{Binding SelectedRule}" Text="{Binding Name}" />
Or, I prefer to use ElementName because it achieves bindings only with view controls.
<TextBlock DataContext="{Binding ElementName=lbRules, Path=SelectedItem}" Text="{Binding Name}" />
There is a shorter version to bind to a selected item's property:
<TextBlock Text="{Binding Rules/Name}" />
since you set your itemsource to your collection, your textbox is tied to each individual item in that collection. the selected item property is useful in this scenario if you were trying to do a master-detail form, having 2 listboxes. you would bind the second listbox's itemsource to the child collection of rules. in otherwords the selected item alerts outside controls that your source has changed, internal controls(those inside your datatemplate already are aware of the change.
and to answer your question yes in most circumstances setting the itemsource is the same as setting the datacontext of the control.

Resources