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.
Related
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).
I have creating following GridRow as UserControl
<UserControl x:Class="Project.Telematics_Plugin.GridRow"
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" BorderBrush="LightBlue"
MaxHeight="30" MinWidth="900">
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsChecked}" />
<TextBox Width="60" Text="{Binding EventId}"/>
<TextBox Width="300" Text="{Binding MethodName}" />
<ComboBox Width="200" ItemsSource="{Binding }" />
<ComboBox Width="200"/>
<ComboBox Width="200"/>
<Button Click="OnClickEdit">
<Image Source="Images/edit.png"/>
</Button>
<Button Click="OnClickDelete">
<Image Source="Images/delete.png"/>
</Button>
</StackPanel>
</Grid>
</UserControl>
Here is the code behind
public partial class GridRow : UserControl
{
public bool IsChecked { get; set; }
public int EventId { get; set; }
public string MethodName { get; set; }
public string Level { get; set; }
public string Opcode { get; set; }
public string Task { get;set; }
public string Keyword { get; set; }
public GridRow()
{
InitializeComponent();
}
private void OnClickEdit(object sender, RoutedEventArgs e)
{
}
private void OnClickDelete(object sender, RoutedEventArgs e)
{
}
}
Now can you please tell what important thing I missed to bind properties of code behind files to UI in TwoWay Mode..
Although this is not the MVVM way..
Add an x:Name to your control and bind to the properties using ElementName:
<UserControl x:Name="MyGridRow">
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsChecked, ElementName=MyGridRow}" />
<TextBox Width="60" Text="{Binding EventId, ElementName=MyGridRow}"/>
<TextBox Width="300" Text="{Binding MethodName, ElementName=MyGridRow}" />
<ComboBox Width="200" ItemsSource="{Binding Path=., ElementName=MyGridRow}" />
<ComboBox Width="200"/>
<ComboBox Width="200"/>
<Button Click="OnClickEdit">
<Image Source="Images/edit.png"/>
</Button>
<Button Click="OnClickDelete">
<Image Source="Images/delete.png"/>
</Button>
</StackPanel>
</Grid>
</UserControl>
If you want to support updating the values, you should use DependencyProperties instead of normal properties:
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(GridRow));
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { GetValue(IsCheckedProperty, value); }
}
when the DataContext where you use your usercontrol has all the properties IsChecked, EventId,MethodName ,..., then you can remove the properties from your usercontrol and all works.
but if you wanna create a "real" usercontrol then you should use DependencyProperties and bind them with the right expression within your usercontrol.
btw when you use Binding in WPF then its all about the right DataContext and the right BindingExpression
I have a UserControl with several TextBox controls and a ProgressBar. The TextBox controls properly reflect the properties in codebehind to which they are bound. The ProgressBar does not respond to property change, however.
My XAML:
<UserControl
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:Controls="clr-namespace:Cmc.Installer.Controls;assembly=Cmc.Installer.Controls" x:Class="Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800">
<Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
<Canvas>
<Label Content="Database Server" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<TextBox Text="{Binding DatabaseServer}" Height="23" Canvas.Left="160" Canvas.Top="12" Width="160"/>
<Label Content="Database Name" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="38"/>
<TextBox Text="{Binding DatabaseName}" Height="23" Canvas.Left="160" Canvas.Top="40" Width="160"/>
<Label Content="Database Username" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="66"/>
<TextBox Text="{Binding DatabaseUsername}" Height="23" Canvas.Left="160" Canvas.Top="68" Width="160"/>
<Label Content="Database Password" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="94"/>
<Controls:BindablePasswordBox Password="{Binding DatabasePassword}" Height="23" Canvas.Left="160" Canvas.Top="96" Width="160"/>
<ProgressBar Name="ProgressBar" Value="{Binding Progress}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
</Canvas>
</Grid>
</UserControl>
And its codebehind (very abbreviated):
public partial class MobileRecruiterModule : UserControl, INotifyPropertyChanged
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private int _progress;
public MobileRecruiterModule()
{
InitializeComponent();
DataContext = this;
}
public string DatabaseServer { get; set; }
public string DatabaseName { get; set; }
public string DatabaseUsername { get; set; }
public string DatabasePassword { get; set; }
public int Progress
{
get { return _progress; }
set
{
if (value == _progress) return;
_progress = value;
OnPropertyChanged("Progress");
Logger.Trace("Progress.set() = " + _progress);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
// This is called by an external class
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
Progress = args.ProgressPercentage;
}
}
I know the value of Progress is changing because I see it in the NLog logs:
2014-04-17 16:22:54.4068|TRACE|Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule|Progress.set() = 28
I don't understand why the ProgressBar doesn't update when I fire OnPropertyChanged in the setter just before the logging call.
I replicated a scaled down version of your app in an MVVM pattern and had good luck with it. I used this code to replicate your user control...
<UserControl x:Class="ProgressBarBinding.Login"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
<Canvas>
<Label Content="Database Server" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<TextBox Text="{Binding DatabaseServer}" Height="23" Canvas.Left="160" Canvas.Top="12" Width="160"/>
<Label Content="Database Name" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="38"/>
<TextBox Text="{Binding DatabaseName}" Height="23" Canvas.Left="160" Canvas.Top="40" Width="160"/>
<Label Content="Database Username" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="66"/>
<TextBox Text="{Binding DatabaseUsername}" Height="23" Canvas.Left="160" Canvas.Top="68" Width="160"/>
<Label Content="Database Password" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="94"/>
<ProgressBar Name="ProgressBar" Value="{Binding Progress}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
</Canvas>
</Grid>
</UserControl>
The only thing missing from that is your proprietary password control, which does not affect the solution.
I encoded this control into a MainWindow.xaml file thusly...
<Window x:Class="ProgressBarBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ProgressBarBinding"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<vm:ViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<vm:Login/>
</Grid>
</Window>
Note that the window resource definition includes a reference to a view model instance. Most people set up MVVM with dependency injection, but this approach is good for quick trials and Indicative Code. The view model is set as the Grid's data context. Your control inherits the data context from the grid. That's the end of the xaml code. There is no code-behind in the MainWindow.xaml.cs file other than the call to InitializeComponent (and that's where the VM instance gets created).
The ViewModel class looks like this...
public class ViewModel : INotifyPropertyChanged
{
private readonly SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
public ViewModel()
{
DatabaseServer = "AnyServer";
DatabaseName = "Any name";
Model m = new Model();
Task.Run(() => m.DoWork(this));
}
public string DatabaseServer { get; set; }
public string DatabaseName { get; set; }
public string DatabaseUsername { get; set; }
public string DatabasePassword { get; set; }
private int _progress;
public int Progress
{
get { return _progress; }
set
{
if (value == _progress) return;
_progress = value;
OnPropertyChanged("Progress");
Console.WriteLine(#"Progress.set() = " + _progress);
}
}
// This is called by an external class
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
_synchronizationContext.Send(delegate { Progress = args.ProgressPercentage; }, null);
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
Most of the code in the view model looks like yours except there are no dependencies on UI elements. Everything is done via binding. I used a SynchronizationContext in the callback, although it may not be necessary in your application.
The constructor of the VM starts a model on a TPL thread. The model looks like this...
public class Model
{
public void DoWork(ViewModel vm)
{
int progressPercentage = 0;
for (int i = 0; i < 100000; i++)
{
vm.OnProgressChanged(this, new ProgressChangedEventArgs(progressPercentage, null));
if (i%1000 == 0)
{
++progressPercentage;
}
}
}
}
So putting it all together, the model is running in its own thread, and the UI is being updated on its own thread. The whole thing works as expected.
The ProgressBar will increment its way up to 100 and the UI will remain responsive while the model is doing its work. This answer does not explain why your original code does not work, but I suspect it has to do with the UI thread being starved out. This is evidenced by your complete log history, but nothing changing on the UI. Overall, this answer moves toward what others have suggested in their commentary: namely that the MVVM approach of binding has a lot to offer.
Since you're in a UserControl, you need to explicitly give it a name and use the ElementName tag when binding, like this:
<UserControl
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:Controls="clr-namespace:Cmc.Installer.Controls;assembly=Cmc.Installer.Controls" x:Class="Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800" x:Name="MyControl">
<Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
<Canvas>
<ProgressBar Name="ProgressBar" Value="{Binding Progress, ElementName=MyControl}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
</Canvas>
</Grid>
</UserControl>
Why do you need Binding if you have the Actual control. Since you are not doing it in MVVM just call the ProgressBar right away.
ProgressBar.Dispatcher.Invoke(() => ProgressBar.Value = Progress = args.ProgressPercentage);
Sorry but I don't see the benefit of the Binding if all the properties/controls are accessible in your View's class.
Binding is more useful and powerful if you implemented MVVM.
I am developing my first Windows 8 app, in one page i am trying to update button text with latest timestop when page loads. I defined my xaml and codebehind like below:
I am using databinding to update the button text but it is not working as expected:
MainPage.xaml
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Button HorizontalAlignment="Left" Margin="333,284,0,0" VerticalAlignment="Top" Height="69" Width="162">
<Button.Resources>
<DataTemplate x:Key="DataTemplate1">
<Grid>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding ButtonText}" VerticalAlignment="Top" Foreground="#FFFF6800" Height="34" Margin="-30,0,-22,-14" Width="115"/>
</Grid>
</DataTemplate>
</Button.Resources>
<Button.ContentTemplate>
<StaticResource ResourceKey="DataTemplate1"/>
</Button.ContentTemplate>
</Button>
</Grid>
MainPage.xaml.cs
public StatsClass Stats { get; private set; }
public MainPage()
{
this.InitializeComponent();
this.DataContext = Stats;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
UpdateButton();
}
private void UpdateButton()
{
if (Stats == null)
Stats = new StatsClass();
Stats.ButtonText = DateTime.Now.ToString();
}
StatsClass.cs
public class StatsClass : INotifyPropertyChanged
{
private string _buttonText;
public string ButtonText
{
get
{
return _buttonText;
}
set
{
_buttonText = value;
OnPropertyChanged("ButtonText");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
You have set Content of your Button twice, once with Content="Button" and again with.Button.ContentTemplate. You could just have:
<Button HorizontalAlignment="Left" Margin="333,284,0,0" VerticalAlignment="Top" Height="69" Width="162">
<Grid>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding ButtonText}" VerticalAlignment="Top" Foreground="#FFFF6800" Height="34" Margin="-30,0,-22,-14" Width="115"/>
</Grid>
</Button>
I had a similar issue yesterday using binding in a DataTemplate. I guess that you also had a binding error in the debug output.
I solved it using a relative source like that:
<TextBlock Text={Binding DataContext.ButtonText,
RelativeSource={RelativeSource FindAncestor, AncestorType=*YourControl*}}"/>
The Template has no direct access to the datacontext. By using the relative source you can bind to its properties.
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}"