Binding to a depencency property doesn't work - c#

I derived ImageButton from Button class. I want to be able to set text on it using custom property.
ImageButton XAML
<Button x:Class="MyProject.ImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="50" Width="75" Template="{DynamicResource BlueButton}">
<StackPanel>
<TextBlock Text="{Binding Path = ImageButton.ButtonText}" TextWrapping="Wrap" Foreground="White"/>
</StackPanel>
</Button>
ImageButton code behind
public partial class ImageButton : Button
{
public string ButtonText
{
get { return (string)GetValue(ButtonTextProperty); }
set { SetValue(ButtonTextProperty, value); }
}
public static readonly DependencyProperty ButtonTextProperty =
DependencyProperty.Register("ButtonText", typeof(string), typeof(ImageButton), new UIPropertyMetadata(string.Empty));
public ImageButton()
{
InitializeComponent();
}
}
Client code:
<local:ImageButton Margin="114,15.879,96,15.878" Grid.Row="2" ButtonText="test"/>
No text is being shown on the button. For some reason the binding doesn't seem to be taking place. What am I doing wrong?

You don’t have a DataContext set, so the data binding doesn’t know which source object it should bind to.
I find that the easiest way to resolve this is to give your outer control a name, and reference it using ElementName in your binding:
<Button x:Class="MyProject.ImageButton"
x:Name="myImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="50" Width="75" Template="{DynamicResource BlueButton}">
<StackPanel>
<TextBlock Text="{Binding ElementName=myImageButton, Path=ButtonText}"
TextWrapping="Wrap" Foreground="White"/>
</StackPanel>
</Button>

Change this
<TextBlock Text="{Binding Path = ImageButton.ButtonText}"
to
<TextBlock Text="{Binding Path = ButtonText}"

Related

WPF Binding to a DependencyProperty problem with an custom User Control inside a Window

I need some help. I created a custom User Control, and inserted it into the Main Window. However, Im not able to bind a Property in the Window to a DependencyProperty in the User Control.
Here's the User Control code.
XAML:
<UserControl x:Class="SomeExample.UCFullName"
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:SomeExample"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Margin="0,0,0,0" Orientation="Vertical" >
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel Orientation="Vertical">
<Label BorderBrush="White" BorderThickness="1" Content="First Name :" FontSize="14" FontWeight="SemiBold" Foreground="White" Height="30" HorizontalAlignment="Left" Margin="0,2,0,0" VerticalAlignment="Top" Width="100"/>
</StackPanel>
<Grid>
<TextBox Name="FirstName" BorderBrush="Black" BorderThickness="1" FontSize="14" FontWeight="SemiBold" Height="30" HorizontalAlignment="Left" Margin="2,2,0,0" MaxLength="20" VerticalAlignment="Top" Width="100" TextChanged="TxtBlock_TextChanged"/>
</Grid>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel Orientation="Vertical">
<Label BorderBrush="White" BorderThickness="1" Content="Last Name :" FontSize="14" FontWeight="SemiBold" Foreground="White" Height="30" HorizontalAlignment="Left" Margin="0,2,0,0" VerticalAlignment="Top" Width="100"/>
</StackPanel>
<Grid>
<TextBox Name="LastName" BorderBrush="Black" BorderThickness="1" FontSize="14" FontWeight="SemiBold" Height="30" HorizontalAlignment="Left" Margin="2,2,0,0" MaxLength="20" VerticalAlignment="Top" Width="100" TextChanged="TxtBlock_TextChanged"/>
</Grid>
</StackPanel>
</StackPanel>
And here's the code behind
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace SomeExample
{
public partial class UCFullName : UserControl, INotifyPropertyChanged
{
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged implementation
public string ValueFullName
{
get { return (string)GetValue(ValueFullNameProperty); }
set
{
SetValue(ValueFullNameProperty, value);
Notify("ValueFullName");
}
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueFullNameProperty =
DependencyProperty.Register("ValueFullName", typeof(string), typeof(UCFullName), new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public UCFullName()
{
InitializeComponent();
}
private void TxtBlock_TextChanged(object sender, TextChangedEventArgs e)
{
ValueFullName = FirstName.Text + " " + LastName.Text;
}
}
}
This is how it looks:
And here's the code of the Main Window:
<Window x:Class="SomeExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SomeExample"
mc:Ignorable="d"
Title="Some Example" Height="200" Width="400">
<StackPanel Margin="0,0,0,0" Orientation="Vertical" Name="SpManual" Background="Black">
<GroupBox Header="Name" Foreground="White" FontSize="14" Name="groupBoxCoordinateStart" >
<local:UCFullName ValueFullName="{Binding Path = PropertyFullName, Mode = TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"></local:UCFullName>
</GroupBox>
<StackPanel Name="SpBtnInsert" Orientation="Horizontal" HorizontalAlignment="Center" Visibility="Visible">
<Button Name="btnShowFullName" BorderBrush="White" BorderThickness="1" FontSize="14" FontWeight="SemiBold" Height="30" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="2,2,0,0" Background="Transparent" Content="Show Name" Foreground="White" Width="98" Click="BtnShowFullName_Click"></Button>
</StackPanel>
</StackPanel>
And the code behind:
using System.Windows;
namespace SomeExample
{
/// <summary>
/// Lógica de interacción para MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public string PropertyFullName { get; set; }
public MainWindow()
{
InitializeComponent();
}
private void BtnShowFullName_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Current full name :" + PropertyFullName);
}
}
}
And of course, I expected that when I pressed the button, I got a message with the full name entered by the user. However, I got nothing.
Edit: Here's the solution to the problem, for people who visit this page with a similar problem.
<local:UCFullName ValueFullName="{Binding Path = PropertyFullName, RelativeSource={RelativeSource AncestorType=Window}}"></local:UCFullName>
You are binding to the wrong AncestorType. Instead of UserControl the type must be Window. Window extends Control but not UserControl.
<local:UCFullName ValueFullName="{Binding Path=PropertyFullName, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=Window}}" />
Also because you set the Binding.Mode to TwoWay, the binding source PropertyFullName must be able to notify the binding target ValueFullName about value changes. To achieve this, you need to implement PropertyFullName as a DependencyProperty to enable two way binding.
As a a side note:
The following code can be problematic
public string ValueFullName
{
get { return (string)GetValue(ValueFullNameProperty); }
set
{
SetValue(ValueFullNameProperty, value);
Notify("ValueFullName"); // This line might never get called
}
}
This is just a CLR wrapper for the actual DependencyProperty and will never be invoked by the framework. When using the binding on this property the wrapper will never get called and therefore the event will never get raised.
As BionicCode has pointed out, you can change the AncestorType. Another option is to set DataContext of the Window.
You can either do it in the constructor
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
or in the XAML.
<Window DataContext="{Binding Mode=OneWay, RelativeSource={RelativeSource Self}}">
This way you don't have to specify source in your bindings (as long as you bind to code-behind properties).

DataContext passthrough with ContentProperty

I would like to create a custom control that simplifies the following code:
<StackPanel>
<DockPanel LastChildFill="True">
<Label>First Name</Label>
<TextBox Margin="2" Text="{Binding Path=FirstName}"></TextBox>
</DockPanel>
<DockPanel LastChildFill="True">
<Label>Last Name</Label>
<TextBox Margin="2" Text="{Binding Path=LastName}"></TextBox>
</DockPanel>
</StackPanel>
My thoughts was to make a UserControl like the following, (Layout is a little bit different, but thats out of scope):
<UserControl x:Class="LabelControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<DockPanel LastChildFill="True">
<Label Content="{Binding Path=Text}" Margin="2" MinWidth="100" HorizontalContentAlignment="Right"></Label>
<Grid Margin="2">
<ContentControl Content="{Binding Path=Control}" ></ContentControl>
</Grid>
</DockPanel>
</UserControl>
The code behind exposes 2 Dependency properties:
Text: the content of the label
Control: the control to be hosted by the content.
The class uses the ContentProperty attribute to map the children to the ContentControl.
Thus allowing me to simplify my StackPanel:
<StackPanel>
<controls:LabelControl Text="First Name">
<TextBox Text="{Binding Path=FirstName}"></TextBox>
</controls:LabelControl>
<controls:LabelControl Text="Last Name">
<TextBox Text="{Binding Path=LastName}"></TextBox>
</controls:LabelControl>
</StackPanel>
The problem I am running in to is the bindings in the the control are not mapping. Is there any way around this? The Label Controls DataContext is overridding the parent controls context.
Here is the code behind for the LabelControl:
[ContentProperty("Control")]
public partial class LabelControl : UserControl
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text", typeof(string), typeof(LabelControl), new PropertyMetadata(default(string)));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty ControlProperty =
DependencyProperty.Register("Control", typeof(Control), typeof(LabelControl), new PropertyMetadata(default(Control)));
public Control Control
{
get { return (Control)GetValue(ControlProperty); }
set { SetValue(ControlProperty, value); }
}
public LabelControl()
{
InitializeComponent();
this.DataContext = this;
}
}
Edit: Output confirms the datacontext is overriding.
BindingExpression path error: 'FirstName' property not found on 'object' ''LabelControl' (Name='')'. BindingExpression:Path=FirstName; DataItem='LabelControl' (Name=''); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
Try changing your binding like this if your LabelControl is contained within Window and it's DataContext has the FirstName property.
<TextBox Text="{Binding Path=FirstName,
RelativeSource={RelativeSource AncestorType=Window}}">
</TextBox>
If you don't want to specify RelativeSource every time, you could use your LabelControl as you do now...
<StackPanel>
<controls:LabelControl Text="First Name">
<TextBox Text="{Binding Path=FirstName}"></TextBox>
</controls:LabelControl>
<controls:LabelControl Text="Last Name">
<TextBox Text="{Binding Path=LastName}"></TextBox>
</controls:LabelControl>
</StackPanel>
...and change the LabelControl's implementation instead.
First, loose the DataContext assignment from the LabelControl's codebehind.
public LabelControl()
{
InitializeComponent();
//this.DataContext = this; // we don't want this
}
Then change the XAML template as
<DockPanel LastChildFill="True">
<Label Content="{Binding Path=Text,
RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"
Margin="2" MinWidth="100" HorizontalContentAlignment="Right">
</Label>
<Grid Margin="2">
<ContentControl
Content="{Binding Path=Control,
RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}">
</ContentControl>
</Grid>
</DockPanel>
Now you should have your DataContext set up right.
I found that using UserControl was not the most ideal solution. It turns out that a templated control allows for the DataBinds to pass through without any hackery (RelativeSource).
[ContentProperty("Control")]
public class LabelControl : Control
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text",
typeof(string), typeof(LabelControl), new PropertyMetadata(default(string)));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty ControlProperty =
DependencyProperty.Register("Control", typeof(Control), typeof(LabelControl), new PropertyMetadata(default(Control)));
public Control Control
{
get { return (Control)GetValue(ControlProperty); }
set { SetValue(ControlProperty, value); }
}
}
In app.xaml:
<Style TargetType="controls:LabelControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:LabelControl">
<DockPanel LastChildFill="True">
<Label Content="{TemplateBinding Text}" MinWidth="100" FontSize="11"></Label>
<Grid Margin="2">
<ContentControl Content="{TemplateBinding Control}"></ContentControl>
</Grid>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Got "XPath can't bind to non-XML object" when try to bind with my control, but works with TextBox

I've the custom control with 3 DependencyProperty:
<UserControl x:Class="WpfDemoApp.NewsCard" [...]>
<Border BorderThickness="10" CornerRadius="10" BorderBrush="Wheat" Background="Wheat" Margin="3">
<StackPanel Background="Wheat">
<TextBlock Text="{Binding Date}" TextAlignment="Left"/>
<TextBlock Text="{Binding Title}" FontWeight="Bold" TextAlignment="Left" TextWrapping="Wrap" Margin="0, 3, 0, 3"/>
<TextBlock Text="Description:" FontWeight="Bold" />
<TextBlock Text="{Binding Text}" TextAlignment="Justify" TextWrapping="Wrap" TextTrimming="WordEllipsis"/>
</StackPanel>
</Border>
</UserControl>
In the c# code:
public partial class NewsCard : UserControl
{
public NewsCard()
{
InitializeComponent();
DataContext = this;
}
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(NewsCard), new UIPropertyMetadata("<title>"));
public string Date
{
get { return (string)GetValue(DateProperty); }
set { SetValue(DateProperty, value); }
}
public static readonly DependencyProperty DateProperty =
DependencyProperty.Register("Date", typeof(string), typeof(NewsCard), new UIPropertyMetadata("01.01.1970"));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(NewsCard), new UIPropertyMetadata("<text>"));
}
I'm using the following XML Data Provider:
<XmlDataProvider x:Key="RssData" XPath="//item" Source="http://www.engadget.com/rss.xml"/>
And try to use it the main window XAML file:
<demo:NewsCard Title="{Binding Source=RssData, XPath=title[1]}" Date="{Binding Source=RssData, XPath=pubDate[1]}" Text="{Binding Source=RssData, XPath=description[1]}"/>
Got the same error message for all properties:
System.Windows.Data Error: 45 : BindingExpression with XPath cannot bind to non-XML object.; XPath='title[1]' BindingExpression:Path=/InnerText; DataItem='String' (HashCode=-696447263); target element is 'NewsCard' (Name=''); target property is 'Title' (type 'String') RssData
When I use totally the same binding expression inside Text property of TextBox control everything works fine. What's wrong when I try to use it with my control? Help is very appreciated!
No idea why it would work on a TextBox, but the Binding Source should be specified as StaticResource. Otherwise the source object is just the string "RssData".
Title="{Binding Source={StaticResource RssData}, XPath=title[1]}"
Finally I found the root of "evil". In my custom component class Context was set to the class self-instance, this prevent XML data to be set as the context for the component.
That's why I got "XPath cannot bind to non-XML object", context points to the object itself and since it is not an XML data - using of XPath raise the error.
Hence I removed the following line:
DataContext = this;
from the constructor and set the relative databinding in the XAML file:
[...]
<StackPanel Background="Wheat">
<TextBlock Text="{Binding Date, RelativeSource={RelativeSource AncestorType={x:Type wpfDemoApp:NewsCard}}}" TextAlignment="Left"/>
<TextBlock Text="{Binding Title, RelativeSource={RelativeSource AncestorType={x:Type wpfDemoApp:NewsCard}}}" FontWeight="Bold" TextAlignment="Left" TextWrapping="Wrap" Margin="0, 3, 0, 3"/>
<TextBlock Text="Description:" FontWeight="Bold" />
<TextBlock Text="{Binding Text, RelativeSource={RelativeSource AncestorType={x:Type wpfDemoApp:NewsCard}}}" TextAlignment="Justify" TextWrapping="Wrap" TextTrimming="WordEllipsis"/>
</StackPanel>
after these changes the component works as expected.
So, for short:
remove explicit Context set in the constructor
set RelativeSource binding in the XAML

Binding controls inside ListBox to a source other than the source of ListBox

I have a ListBox bound to a source which provides data to the text property of the controls inside. Now I'd like to bind Foreground property of my textboxes to a different source other than the one the main ListBox is bound to!
My listbox is bound to a ObservableCollection and I want my textblock Foreground property bound to textColor which is located in ViewModel
public SolidColorBrush textColor
{
get { return new SolidColorBrush(Colors.Red); }
}
both are in ViewModel class.
I tried using Foreground="{Binding textColor}" but it seems XAML doesn't see it at all, should I do anything in the page so that it can see it, or is it because the parent (ListBox) is using a different source?
Edit :
More details:
I have a DataContext.cs class which I have defined my tables in it.
I have a ViewModel.cs class which I have these in it
public class CViewModel : INotifyPropertyChanged
{
private CDataContext myDB;
public CViewModel(string DBConnectionString)
{
myDB = new CDataContext(DBConnectionString);
}
private ObservableCollection<Groups> _allGroups;
public ObservableCollection<Groups> AllGroups
{
get { return _allGroups; }
set
{
_allGroups = value;
NotifyPropertyChanged("AllGroups");
}
}
public string textColor
{
get { return "Tomato"; }
}
}
Then I have my XAML file MainPage.xaml:
....
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Margin="0,8,0,0" toolkit:TiltEffect.IsTiltEnabled="True" x:Name="list" ItemsSource="{Binding AllGroups}" HorizontalAlignment="Center" BorderThickness="4">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Orange" Width="125" Height="125" Margin="6" Tap="Counterlist_OnTap">
<TextBlock Name="gname" Foreground="White" Text="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center" TextWrapping="Wrap"/>
<TextBlock Name="ccl" Margin="0,0,0,-5" Foreground="{Binding textColor}" Text="{Binding Count}" FontSize="26" VerticalAlignment="Bottom" HorizontalAlignment="Left" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
....
I also set DataContext of my MainPage to ViewModel in code behind:
this.DataContext = App.ViewModel;
The textColor property is part of the CViewModel, not the Groups object that is the datacontext within the ItemTemplate.
Within the ItemTemplate you can reach out to the parent ViewModel with the following Element binding:
<TextBlock Name="ccl" Margin="0,0,0,-5"
Foreground="{Binding ElementName=ContentPanel, Path=DataContext.textColor}"
Text="{Binding Count}" FontSize="26"
VerticalAlignment="Bottom" HorizontalAlignment="Left" />
All you have to do is declare a static class (e.g. a singleton with per-instance access) and explicitly set the property binding to look in that class instead of the parent-bound model.
Bottom line: Just set the Source explicitly through a StaticResource.

How to implement 2-way binding between TextBox and double value?

Here's the code for my window:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="leartWPF.ControlTestWindow"
x:Name="Window"
Title="ControlTestWindow"
Width="640" Height="480">
<Grid x:Name="LayoutRoot">
<TextBlock Height="26" Margin="45,26,241,0" TextWrapping="Wrap" Text="Please, enter an ariphmetical expression to calculate:" VerticalAlignment="Top"/>
<TextBox Margin="48,72,63,201" TextWrapping="Wrap" Text="{Binding Input, ElementName=Window, FallbackValue=1+1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" TextChanged="TextBox_TextChanged" >
</TextBox>
<!--<TextBlock Margin="282,208,266,167" TextWrapping="Wrap" Text="=" FontSize="64"/>-->
<TextBlock Height="90" Margin="83,0,77,60" TextWrapping="Wrap" VerticalAlignment="Bottom" FontSize="48" Text="{Binding Result, ElementName=Window, Mode=TwoWay}"/>
<Button Content="=" Height="27" Margin="233,0,263,166" VerticalAlignment="Bottom" FontSize="16"/>
</Grid>
</Window>
and the class:
public partial class ControlTestWindow : Window
{
private string _input;
public double Result { get; set; }
private static VsaEngine _engine = VsaEngine.CreateEngine();
public string Input
{
get { return _input; }
set
{
Result = double.Parse(Eval.JScriptEvaluate(value, _engine).ToString());
_input = value;
}
}
public ControlTestWindow()
{
this.InitializeComponent();
// Insert code required on object creation below this point.
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
}
}
The Input gets updated, and Result value changes, but it is never displayed on the appropriate TextBlock.
What should I change for this to work?
The TextBlock doesn't get notified of the change to the Result property. You have two options:
Implement the property as a DependencyProperty. Visual studio has a code snippet for it. Type propdp and you'll see it pop up in intellisense.
Implement INotifyPropertyChanged on your Window class and use it in your property.

Categories

Resources