I used this tutorial to build a custom control. Now, I'd like to add a simple message (a textblock) to the user control to give the user some guidance. I think I can add a public property, like FileName in the tutorial, but how do I wire up the textblock's Text property to the property in the code behind? And then make sure the textblock message updates if the property changes.
I like the idea of being able to set the message in code, via a property, because I will likely have multiple controls of this custom control type on a page. I'm just a bit stumped on wiring it up.
Thanks!
This would be your code behind, which implements INotifyPropertyChanged:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _fileName;
/// <summary>
/// Get/Set the FileName property. Raises property changed event.
/// </summary>
public string FileName
{
get { return _fileName; }
set
{
if (_fileName != value)
{
_fileName = value;
RaisePropertyChanged("FileName");
}
}
}
public MainWindow()
{
DataContext = this;
FileName = "Testing.txt";
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This would be your XAML that binds to the property:
<TextBlock Text="{Binding FileName}" />
EDIT:
Added DataContext = this; i don't normally bind to the code behind (I use MVVM).
Related
I am trying to work out wpf with some difficulties. This ComboBox seems a very basic issue but I can't have it populated even after reading all possible similar post.
The extra difficulty I think is that the ComboBox is defined in a resource, here is the resource code:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:DiagramDesigner">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles/Shared.xaml"/>
<ResourceDictionary Source="Styles/ToolBar.xaml"/>
</ResourceDictionary.MergedDictionaries>
<ToolBar x:Key="MyToolbar" Height="120">
<!--Languages-->
<GroupBox Header="Localization" Style="{StaticResource ToolbarGroup}" Margin="3">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Center"
VerticalAlignment="Top" Width="120"
ItemsSource="{Binding _langListString}"
DisplayMemberPath="ValueString"
SelectedValuePath="ValueString"
SelectedValue="{Binding LangString}"
/>
</Grid>
</GroupBox>
</ToolBar>
My data object is defined as follow:
public partial class Window1 : Window
{
List<ComboBoxItemString> _langListString = new List<ComboBoxItemString>();
// Object to bind the combobox selections to.
private ViewModelString _viewModelString = new ViewModelString();
public Window1()
{
// Localization settings
_langListString.Add(new ComboBoxItemString()); _langListString[0].ValueString = "en-GB";
_langListString.Add(new ComboBoxItemString()); _langListString[1].ValueString = "fr-FR";
_langListString.Add(new ComboBoxItemString()); _langListString[2].ValueString = "en-US";
// Set the data context for this window.
DataContext = _viewModelString;
InitializeComponent();
}
And the modelview:
/// This class provides us with an object to fill a ComboBox with
/// that can be bound to string fields in the binding object.
public class ComboBoxItemString
{
public string ValueString { get; set; }
}
//______________________________________________________________________
//______________________________________________________________________
//______________________________________________________________________
/// Class used to bind the combobox selections to. Must implement
/// INotifyPropertyChanged in order to get the data binding to
/// work correctly.
public class ViewModelString : INotifyPropertyChanged
{
/// Need a void constructor in order to use as an object element
/// in the XAML.
public ViewModelString()
{
}
private string _langString = "en-GB";
/// String property used in binding examples.
public string LangString
{
get { return _langString; }
set
{
if (_langString != value)
{
_langString = value;
NotifyPropertyChanged("LangString");
}
}
}
#region INotifyPropertyChanged Members
/// Need to implement this interface in order to get data binding
/// to work properly.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I just don't know what to try else. Is anyone has an idea of what is going on, and why the combobox stays empty?
Many thanks.
you can just bind to public properties
ItemsSource="{Binding _langListString}"
can not work because _langListString is not a public property
By my analysis the problem consist in your DataContext.
DataContext = _viewModelString;
If you give the viewModelString to the DataContext you have to have the _langListString >defined there, in order to the combobox know which item it is bound to.
This is what I would do:
Add List _langListString = new List(); to the >ModelView.
_langListString would be _viewModelString._langListString.add(Your Items) - be >carefull to instatiate the _langList when you create your _viewModelString object.
Then I think the rest would work.
Many thanks, I have the changes you've suggested but this combobox still stays empty :-(
The new modelview looks like this:
/// Class used to bind the combobox selections to. Must implement
/// INotifyPropertyChanged in order to get the data binding to
/// work correctly.
public class ViewModelString : INotifyPropertyChanged
{
public List<ComboBoxItemString> _langListString {get;set;}
/// Need a void constructor in order to use as an object element
/// in the XAML.
public ViewModelString()
{
// Localization settings
_langListString = new List<ComboBoxItemString>();
ComboBoxItemString c;
c = new ComboBoxItemString(); c.ValueString = "en-GB"; _langListString.Add(c);
c = new ComboBoxItemString(); c.ValueString = "fr-FR"; _langListString.Add(c);
c = new ComboBoxItemString(); c.ValueString = "en-US"; _langListString.Add(c);
}
private string _langString = "en-GB";
/// String property used in binding examples.
public string LangString
{
get { return _langString; }
set
{
if (_langString != value)
{
_langString = value;
NotifyPropertyChanged("LangString");
}
}
}
#region INotifyPropertyChanged Members
/// Need to implement this interface in order to get data binding
/// to work properly.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
The data object:
// Object to bind the combobox selections to.
private ViewModelString _viewModelString;
public Window1()
{
// Set the data context for this window.
_viewModelString = new ViewModelString();
DataContext = _viewModelString;
InitializeComponent();
}
And I have tried all possible combination in the combobox (_langListString, _viewModelString._langListString, _viewModelString) it just doesn't work:
<ComboBox Height="23" HorizontalAlignment="Center"
VerticalAlignment="Top" Width="120"
ItemsSource="{Binding _langListString}"
DisplayMemberPath="ValueString"
SelectedValuePath="ValueString"
SelectedValue="{Binding LangString}"
/>
I tend to think that this xaml is making things really complicated without possibility of debugging. Is anyone can help???
I have a class named Data with some public members: Name, Age, Address.
I have also window with text boxes Name, Age, Address.
The Data object can change any time.
How can I bind the Data object to the text boxes and follow after object changes?
I know there is INotifyPropertyChanged and "dependency-properties" but I do not know how to use them.
Edit
public class MyData : INotifyPropertyChanged
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnPropertyChnged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
ProppertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(name));
}
}
XAML code:
xmlns:myApp="clr-namespace:MyApp"
<Window.Resources><myApp:MyData x:key = data/></WindowResources>
<TextBox><TextBox.Text><Binding Source="{StaticResource data}" Path="Name" UpdateSourceTrigger="PropertyChanged"/></TextBox.Text></TextBox>
class OtherClass
{
private MyData data;
//the window that have the binding textbox
private MyWindow window;
public OtherClass()
{
data = new MyData();
data.Name = "new name"
window = new MyWindow();
window.show();
}
}
This link from MSDN explains it well.
MSDN link is dead, adding link to a similar article.
When your class property is changed, your property should raise a OnPropertyChanged event with the name of the property so that the View knows to refresh it's binding.
public String Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
this.OnPropertyChanged("Name");
}
}
}
And your textbox should have a binding such as:
<TextBox Text="{Binding Name}"/>
I have a ViewModelBase class which is where I have implemented my OnPropertyChandedEvent for all derived models to call:
/// <summary>
/// An event for when a property has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Virtual method to call the Property Changed method
/// </summary>
/// <param name="propertyName">The name of the property which has changed.</param>
protected virtual void OnPropertyChanged(String propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Let's the Data class implements INotifyPropertyChanged . Raise the event when someone change the property value on the instances of Data. Then set the proper DataContext to your UI, and bind the single ui element as for example:
<TextBox Text="{Binding Age}"/>
My application's main component is a tab control which holds N number of views and those views' datacontext is a separate ViewModel object. I have a statusbar at the bottom of the app and it contains a few textboxes. I want one of the textboxes to reflect a timestamp for the currently selected tab. The timestamp is a property of the ViewModel object that's set as the view's datacontext.
I'm a WPF newb and not really sure how to bind that property to the status bar.
Make sure your ViewModel implements INotifyPropertyChanged.
For example...
/// <summary>
/// Sample ViewModel.
/// </summary>
public class ViewModel : INotifyPropertyChanged
{
#region Public Properties
/// <summary>
/// Timestamp property
/// </summary>
public DateTime Timestamp
{
get
{
return this._Timestamp;
}
set
{
if (value != this._Timestamp)
{
this._Timestamp = value;
// NOTE: This is where the ProperyChanged event will get raised
// which will result in the UI automatically refreshing itself.
OnPropertyChanged("Timestamp");
}
}
}
#endregion
#region INotifyPropertyChanged Members
/// <summary>
/// Event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise the PropertyChanged event.
/// </summary>
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Private Fields
private DateTime _Timestamp;
#endregion
}
Something like:
<TextBox Text="{Binding ElementName=tabControl, Path=SelectedItem.DataContext.Timestamp}" />
A little depending on if your tabcontrol's itemssource is databound or not.
In a Silverlight MVVMLight 4.0 application I have a listbox, a textbox and a checkbox.
The listbox's ItemsSource is bound to a list of objects in the viewmodel.
The listbox's SelectedItem is two-way bound to an object (SelectedActivity) in the viewmodel.
Both the textbox's Text and the checkbox's IsSelected properties are two-way bound to the SelectedActivity object (Name and Selected properties) in the viewmodel.
There is no codebehind.
This works fine: changing the Name in the textbox or checking/unchecking the checkbox and then tabbing will change the underlying property of the object.
But when I change the name (or the checked state) and then immediatelly click another item in the list, the change is not registered.
Does anybody have a workaround for this?
kind regards,
Karel
This is the XAML:
<ListBox Height="251" HorizontalAlignment="Left" Margin="11,39,0,0" Name="activitiesListBox" ItemsSource="{Binding Activities.Items}" VerticalAlignment="Top" Width="139"
SelectedItem="{Binding Activities.SelectedActivity, Mode=TwoWay}">
This is the Activities class holding the items bound to the list:
public class CLJActivitiesViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the ActivitiesViewModel class.
/// </summary>
public CLJActivitiesViewModel()
{
////if (IsInDesignMode)
////{
//// // Code runs in Blend --> create design time data.
////}
////else
////{
//// // Code runs "for real": Connect to service, etc...
////}
}
#region items
/// <summary>
/// The <see cref="Items" /> property's name.
/// </summary>
public const string ItemsPropertyName = "Items";
private ObservableCollection<CLJActivityViewModel> m_Items = null;
/// <summary>
/// Gets the Items property.
/// TODO Update documentation:
/// Changes to that property's value raise the PropertyChanged event.
/// This property's value is broadcasted by the Messenger's default instance when it changes.
/// </summary>
public ObservableCollection<CLJActivityViewModel> Items
{
get
{
return m_Items;
}
set
{
if (m_Items == value)
{
return;
}
var oldValue = m_Items;
m_Items = value;
RaisePropertyChanged(ItemsPropertyName, oldValue, value, true);
}
}
#endregion
#region SelectedActivity
/// <summary>
/// The <see cref="SelectedActivity" /> property's name.
/// </summary>
public const string SelectedActivityPropertyName = "SelectedActivity";
private CLJActivityViewModel m_SelectedActivity = null;
/// <summary>
/// Gets the SelectedActivity property.
/// TODO Update documentation:
/// Changes to that property's value raise the PropertyChanged event.
/// This property's value is broadcasted by the Messenger's default instance when it changes.
/// </summary>
public CLJActivityViewModel SelectedActivity
{
get
{
return m_SelectedActivity;
}
set
{
if (m_SelectedActivity == value)
{
return;
}
var oldValue = m_SelectedActivity;
m_SelectedActivity = value;
RaisePropertyChanged(SelectedActivityPropertyName, oldValue, value, true);
}
}
#endregion
public override void Cleanup()
{
// Clean own resources if needed
base.Cleanup();
}
}
I ran into the kinda the same issue. I had to trigger the update as the user was entering text so that I could do some validation.
An easy way to achieve that is to create a custom behaviour that you can then add to any TextBox.
Mine is as follows:
public static class TextChangedBindingBehavior
{
public static readonly DependencyProperty InstanceProperty =
DependencyProperty.RegisterAttached("Instance", typeof(object), typeof(TextChangedBindingBehavior), new PropertyMetadata(OnSetInstanceCallback));
public static object GetInstance(DependencyObject obj)
{
return (object)obj.GetValue(InstanceProperty);
}
public static void SetInstance(DependencyObject obj, object value)
{
obj.SetValue(InstanceProperty, value);
}
private static void OnSetInstanceCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = d as TextBox;
if (textBox != null)
{
textBox.TextChanged -= OnTextChanged;
textBox.TextChanged += OnTextChanged;
}
}
private static void OnTextChanged(object sender, TextChangedEventArgs e)
{
var textBox = (TextBox)sender;
if(!DesignerProperties.GetIsInDesignMode(textBox))
{
textBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
}
and you set it to the TextBox like that (Behaviors is the namespace where I put the class above):
<TextBox Behaviors:TextChangedBindingBehavior.Instance="" Text="{Binding Name, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" />
I've ran into the issue like that with TextBox, but didn't see it affecting check box. TextBox issue is happening because bound text gets updated then focus is lost. That is why if you tab first and then change your selection it works as you expect. If you change selection directly bound text doesn't get updated since focus lost message arrives too late.
One way of dealing with this issue is to force binding update every time user types text in the text box. You can make custom behaviour to keep it mvvm.
I'm trying to bind my window title to a property in my view model, like so:
Title="{Binding WindowTitle}"
The property looks like this:
/// <summary>
/// The window title (based on profile name)
/// </summary>
public string WindowTitle
{
get { return CurrentProfileName + " - Backup"; }
}
The CurrentProfileName property is derived from another property (CurrentProfilePath) that is set whenever someone opens or saves profile. On initial startup, the window title is set properly, but when ever the CurrentProfilePath property changes, the change doesn't bubble up to the window title like I expected it would.
I don't think I can use a dependency property here because the property is a derived one. The base property from which it is derived is a dependency property, but that doesn't seem to have any effect.
How can I make the form title self-updating based on this property?
That's because WPF has no way of knowing that WindowTitle depends on CurrentProfileName. Your class needs to implement INotifyPropertyChanged, and when you change the value of CurrentProfileName, you need to raise the PropertyChanged event for CurrentProfileName and WindowTitle
private string _currentProfileName;
public string CurrentProfileName
{
get { return __currentProfileName; }
set
{
_currentProfileName = value;
OnPropertyChanged("CurrentProfileName");
OnPropertyChanged("WindowTitle");
}
}
UPDATE
Here's a typical implementation of INotifyPropertyChanged :
public class MyClass : INotifyPropertyChanged
{
// The event declared in the interface
public event PropertyChangedEventHandler PropertyChanged;
// Helper method to raise the event
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName);
}
...
}