i wrote a simple custom control that inherits from textbox and uses a custom inputbox to enter text:
class TextTextbox : TextBox
{
public string InputBoxTitle { get; set; }
public string Input { get; set; }
public TextTextbox()
{
PreviewMouseDown += MyTextbox_MouseDown;
}
private void MyTextbox_MouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
TextBox tb = (TextBox)sender;
var dialog = new BFH.InputBox.InputBox(InputBoxTitle, Input);
dialog.ShowDialog();
if (!dialog.Canceled)
tb.Text = dialog.Input;
else
tb.Text = Input;
}
}
i use it in the view like this:
<CustomControls:TextTextbox Text="{Binding Test}" InputBoxTitle="Titel" Input="Input"/>
in the vm for tests:
private string _test;
public string Test
{
get { return _test; }
set
{
if (_test == value)
return;
_test = value;
RaisePropertyChanged("Test");
}
}
but the binding to Test is not working. in the view, i see that the text of the textbox does change, but i seems not to be linked to the property of the VM. i added a button with MessageBox.Show(Test), but it is always empty. what am i doing wrong here?
You need to set the binding's UpdateSourceTrigger property to PropertyChanged. Otherwise the source property Test will not be updated before the TextBox loses focus.
<CustomControls:TextTextbox
Text="{Binding Test, UpdateSourceTrigger=PropertyChanged}" ... />
From the Examples section on the Binding.UpdateSourceTrigger page on MSDN:
The TextBox.Text property has a default UpdateSourceTrigger value of
LostFocus. This means if an application has a TextBox with a
data-bound TextBox.Text property, the text you type into the TextBox
does not update the source until the TextBox loses focus (for
instance, when you click away from the TextBox).
Related
I'm developing a WPF application and I'm struggling a little bit to understand some of the details of DataContext as it applies to binding. My application uses a business object which is defined like this:
public class MyBusinessObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
// enumerations for some properties
public enum MyEnumValues
{
[Description("New York")]
NewYork,
[Description("Chicago")]
Chicago,
[Description("Los Angeles")]
LosAngeles
}
// an example property
private string _myPropertyName;
public string MyPropertyName
{
get { return _myPropertyName; }
set
{
if (_myPropertyName == value)
{
return;
}
_myPropertyName = value;
OnPropertyChanged(new PropertyChangedEventArgs("MyPropertyName"));
}
}
// another example property
private MyEnumValues _myEnumPropertyName;
public MyEnumValues MyEnumPropertyName
{
get { return _myEnumPropertyName; }
set
{
if (_myEnumPropertyName== value)
{
return;
}
_myEnumPropertyName= value;
OnPropertyChanged(new PropertyChangedEventArgs("MyEnumPropertyName"));
}
}
// example list property of type Widget
public List<Widget> MyWidgets { get; set; }
// constructor
public MyBusinessObject()
{
// initialize list of widgets
MyWidgets = new List<Widget>();
// add 10 widgets to the list
for (int i = 1; i <= 10; i++)
{
MyWidgets.Add(new Widget());
}
// set default settings
this.MyPropertyName = string.empty;
}
}
As you can see, I have some properties that are declared in this class one of which is a list of Widgets. The Widget class itself also implements INotifyPropertyChanged and exposes about 30 properties.
My UI has a combobox which is bound to my list of Widgets like this:
MyBusinessObject myBusinessObject = new MyBusinessObject();
public MainWindow()
{
InitializeComponent();
this.DataContext = myBusinessObject;
selectedWidgetComboBox.ItemsSource = myBusinessObject.MyWidgets;
selectedWidgetComboBox.DisplayMemberPath = "WidgetName";
selectedWidgetComboBox.SelectedValuePath = "WidgetName";
}
The majority of the controls on my UI are used to display the properties of a Widget. When my user selects a Widget from the combobox, I want these controls to display the properties for the selected Widget. I'm currently achieving this behavior by updating my window's DataContext in the SelectionChanged event handler of my combobox like this:
private void selectedWidgetComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.DataContext = selectedWidgetComboBox.SelectedItem;
}
This allows me to bind my controls to the appropriate Widget property like this:
<TextBox Text="{Binding WidgetColor}"></TextBox>
However, not all of the controls in my UI are used to display Widget properties. Some of the controls need to display the properties from MyBusinessObject (for example: MyPropertyName defined above). In this scenario, I can't simply say:
<TextBox Text="{Binding MyPropertyName}"></TextBox>
...because the DataContext of the window is pointing to the selected Widget instead of MyBusinessObject. Can anyone tell me how I set the DataContext for a specific control (in XAML) to reference the fact that MyPropertyName is a property of MyBusinessObject? Thank you!
Instead of changing the DataContext of your window, you should add a property to your MyBusinessObject class like this one:
private Widget _selectedWidget;
public Widget SelectedWidget
{
get { return _selectedWidget; }
set
{
if (_selectedWidget == value)
{
return;
}
_selectedWidget = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedWidget"));
}
}
Then bind SelectedWidget to the SelectedItem property of your combobox. Anywhere that you need to use the widget's properties you can do this:
<TextBox Text="{Binding Path=SelectedWidget.WidgetColor}"></TextBox>
try
<TextBox Text="{Binding MyBusinessObject.MyPropertyName}"></TextBox>
this works if MyBusinessObject is the datacontext of the textbox and MyPropertyName is a property of MyBusinessObject
Also, Here is a good article to clarify binding
hope this helps
EDIT 1:
use a relative binding like this:
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TypeOfControl}}}"
So the relatve binding allows you to look up the visual tree to another UI element and use its datacontext. I would consider wrapping your window's contents in a grid. and wet your windows datacontext to the businessobject and the grids datacontext to the widget. That way you can always use the parent window's datacontext through the realtive source binding.
so use the following if your window's datacontext is your business object
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
I have a DataGrid which is bound to an ObservableCollection ProductsFound
which is exposed as a property in my ViewModel.
By typing text in a TextBox, products contained in the model that have the Code property that contains the text inserted in the TextBox are added to ProductsFound.
I found out that if the DataGrid is contained in any control such as a StackPanel or a TabItem, the Window (the program) stops responding when I try to type text into the TextBox; while if the DataGrid isn't contained in any control, everything runs normally.
Here's the code for the window:
public class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// This method just fill the dataset I pass to the model's contructor in the next line.
Init();
ProductsModel model = new ProductsModel(dataSet);
searchViewModel = new ProductsSearchViewModel(model);
DataContext = searchViewModel;
}
private ProductsSearchViewModel searchViewModel;
// This handler supports the binding between the TextBox and the MatchText property of the View Model.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
}
}
And here's my ViewModel:
public class ProductsSearchViewModel : Notifier, IProductsSearchViewModel
{
public ProductsSearchViewModel(IProductsModel inModel)
{
model = inModel;
productsFound = new ObservableCollection<ProductViewModel>();
}
private string matchText;
private IProductsModel model;
private ObservableCollection<ProductViewModel> productsFound;
// This is a helper method that search for the products in the model and adds them to ProductsFound.
private void Search(string text)
{
Results.Clear();
foreach (Product product in model.Products)
{
if (product.Code.ToLower().Contains(text.ToLower()))
Results.Add(new ProductViewModel(product));
}
}
public string MatchText
{
get { return matchText; }
// This setter is meant to be executed every time the Text property of the TextBox is changed.
set
{
if ((value != matchText) && (value != ""))
{
matchText = value;
// This raises INotifyPropertyChanged.PropertyChaged.
NotifyPropertyChanged("MatchText");
Search(value);
}
}
}
public ObservableCollection<ProductViewModel> ProductsFound
{
get
{
return productsFound;
}
set
{
productsFound = value;
NotifyPropertyChanged("Results");
}
}
}
Here's the XAML:
<Window x:Class="MyNameSpace.UI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<TextBox Text="{Binding MatchText, Mode=TwoWay}" TextChanged="TextBox_TextChanged" />
<DataGrid x:Name="grid1" ItemsSource="{Binding Results}" >
</StackPanel>
</Grid>
With that StackPanel the program stops responding when I try to type text in the Textbox and no item is added to the DataGrid; but if i remove it everything runs ok.
What could the problem be? Am I missing something in how the WPF binding system works?
Is my view model coded wrong?
Thanks in advance.
Putting that StackPanel there prevents the DataGrid from acquiring a specific Height, thus it just expands down to infinity, and that breaks UI Virtualization.
Remove the StackPanel from there and use a non-infinite container, such as Grid or DockPanel.
I have created a custom TextBox control (but not derived from TextBox) that contains a Dependency Property "Text".
I have added an instance of this and bound it to a property on my view model using a TwoWay binding.
From within my custom TextBox control, how do I update the Text property in such a way that the change is propagated to the property on the view model?
If I set the "Text" property on my custom control, that replaces the binding leaving the property on the view model as null.
I would have thought this would be simple but I can't see how to do it (the standard TextBox control must do it!)
Cheers
Edit:
Custom Control:
public class SampleCustomControl : CustomControl
{
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(SampleCustomControl), new PropertyMetadata(null));
public void Update()
{
// This replaces my binding, I want it to pass the new value
// through to the "SomeProperty" two way binding.
Text = "some value";
}
}
Usage:
<Controls:SampleCustomControl Text="{Binding SomeProperty, Mode=TwoWay}" />
You need to add a Property Changed callback in the metadata of your dependency property.
This callback will be fired when the Text property changes (from either side). You can use the value passed in from this to update your custom UI that you've built to display the text.
Update:
Responding to your comment about what this is about. Since your example code is too vague to test, here is what I used to test your problem.
public class TestControl : ContentControl
{
private TextBlock _tb;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_tb = new TextBlock();
_tb.Text = Text;
this.Content = _tb;
_tb.MouseLeftButtonDown += new MouseButtonEventHandler(_tb_MouseLeftButtonDown);
}
void _tb_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Update();
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(TestControl), new PropertyMetadata(string.Empty, OnTextChanged));
public void Update()
{
// This replaces my binding, I want it to pass the new value
// through to the "SomeProperty" two way binding.
Text = "some value";
}
public static void OnTextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
((TestControl)sender).UpdateText((string)e.NewValue);
}
protected void UpdateText(string text)
{
if (_tb != null) _tb.Text = text;
}
}
I then bound the Text property on my control to the view model using a two way binding. When I click the text in the view both the view and the viewmodel get updated with the new text "some value". If I update the value in the viewmodel (and raise the property changed event) the value gets updated in the view and the control so the binding is still valid.
There must be some other missing pieces in your example.
As long as your binding property is set to TwoWay and you have exposed the getter and the setter, than the text you enter in the TextBox is sent to the ViewModel. I believe the actual send occurs when you lose focus of that control however, i believe.
I've been exploring the Caliburn Micro MVVM Framework just to get a feel for it, but I've run into a bit of a problem. I have a TextBox bound to a string property on my ViewModel and I would like the property to be updated when the TextBox loses focus.
Normally I would achieve this by setting the UpdateSourceTrigger to LostFocus on the binding, but I don't see any way to do this within Caliburn, as it has setup the property binding for me automatically. Currently the property is updated every time the content of the TextBox changes.
My code is very simple, for instance here is my VM:
public class ShellViewModel : PropertyChangeBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyOfPropertyChange(() => Name);
}
}
}
And inside my view I have a simple TextBox.
<TextBox x:Name="Name" />
How to I change it so the Name property is only updated when the TextBox loses focus, instead of each time the property changes?
Just set the binding explictly for that instance of the TextBox and Caliburn.Micro won't touch it:
<TextBox Text="{Binding Name, UpdateSourceTrigger=LostFocus}" />
Alternatively, if you want to change the default behaviour for all instances of TextBox, then you can change the implementation of ConventionManager.ApplyUpdateSourceTrigger in your bootstrapper's Configure method.
Something like:
protected override void Configure()
{
ConventionManager.ApplyUpdateSourceTrigger = (bindableProperty, element, binding) =>{
#if SILVERLIGHT
ApplySilverlightTriggers(
element,
bindableProperty,
x => x.GetBindingExpression(bindableProperty),
info,
binding
);
#else
if (element is TextBox)
{
return;
}
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
#endif
};
}
I have a TextBlock, binded with an Object and when i update property of object its not refleting on UI, Why ?
Code:
In Windows1.xaml
<TextBlock Name="txtName" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Width="100" Height="20" Margin="12,23,166,218" />
and In Windows.xaml.cs
public partial class Window1 : Window
{
Employee obj ;
public Window1()
{
InitializeComponent();
obj = new Employee();
obj.Name = "First";
txtName.DataContext = obj;
}
private void btnUpdate_Click(object sender, RoutedEventArgs e)
{
obj.Name = "changed";
}
}
public class Employee : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
set
{
this._name = value;
OnPropertyChanged(Name);
}
get { return this._name; }
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
OnPropertyChanged(Name);
should be:
OnPropertyChanged("Name");
otherwise if the name is set to "Kent", you're raising a property changed event saying that the "Kent" property has changed, which obviously doesn't exist.
As for UpdateSourceTrigger, that only applies to the source. The property you've bound is the target, not the source. And it doesn't make sense for a TextBlock to update its source, because there's no way for the user to modify the TextBlock text. A TextBox, on the other hand, would make sense. In that case, UpdateSourceTrigger determines the point at which the text in the TextBox is pushed back to the source property (e.g. as the user types each character, or when they move away from the TextBox).
Pass the name of the property as string, instead of the property value, like so:
OnPropertyChanged("Name");
UpdateSourceTrigger is for binding with the source property i.e normal .net property so to it requires to set on options like property change with mode twoway for dynamic change ex.text of textbox being change and the which updtaes a label to change. Where as if you want the change event to fire at the end i.e lost focus or click use explicit option with updatesourcetrigger.