I have an issue whereby I am not receiving updates through my bindings.
I have a label which is bound to the ExtentWidth of the TextBox property via the DataContext.
My binding initially works and displays the value of 0 in the label however it does not update after this.
ExtentWidth is a read only property, I'm not sure if this affects the binding in any way but I have a label the binds to the text when it is set so I know it can receive updates. (button updates text and label is updated)
below is some code to demonstrate my issue.
Xaml
<Window x:Class="TestHarnesses.Views.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">
<Grid>
<StackPanel>
<ContentPresenter x:Name="ContentPresenter" Content="{Binding}"></ContentPresenter>
<Label x:Name="lblExtentWidth"
Content="{Binding ExtentWidth, Mode=OneWay}"/>
<Label x:Name="lblText"
Content="{Binding Text, Mode=OneWay}"/>
<Button Content="Different Jibber Jabber" Click="ButtonBase_OnClick"/>
</StackPanel>
</Grid>
</Window>
Code behind
using System.Windows;
using System.Windows.Controls;
namespace TestHarnesses.Views
{
///
<summary>
/// Interaction logic for Window1.xaml
///</summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TextBox tb = new TextBox(){Text = "Jibber Jabber"};
this.TestTextBox = tb;
}
public TextBox TestTextBox
{
get { return (TextBox)GetValue(TestTextBoxProperty); }
set { SetValue(TestTextBoxProperty, value); }
}
// Using a DependencyProperty as the backing store for TestTextBox. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TestTextBoxProperty =
DependencyProperty.Register("TestTextBox", typeof(TextBox), typeof(Window1), new PropertyMetadata(OnTestTextBoxProperty));
private static void OnTestTextBoxProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Window1) d).DataContext = (TextBox) e.NewValue;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
TestTextBox.Text = "Different Jibber Jabber";
}
}
}
Your View is not being notified about changes to the ExtentWidth property because ExtentWidth is not a DependencyProperty, nor does the TextBox class implement INotifyPropertyChanged. Also, there does not appear to be a corresponding Changed event associated with this property.
If you want to update your view automatically with the most recent ExtentWidth then you'll need to listen to a different property/event (perhaps the SizeChanged event?) which gets updated simultaneously.
Related
I often use MahApps.Metro.Controls.NumericUpDown to manipulate numerical values from the user interface. Recently I have observed a binding behaviour so strange and surreal that completely undermined my faith in the WPF binding infrastructure.
The phenomenon occurred with a collection of view models bound to the ItemsSource of a TabControl. Within each of these view models, there is a property of type int that I bind to the Value property of a MahApps.Metro.Controls.NumericUpDown. I noticed that when I manipulate a NumericUpDown through its TextBox and leave the focus there just before I select a new tab in the TabControl, the value I have just set gets written into the NumericUpDown inside the new tab being selected. That is clearly undesirable as that tab contains a value I did not intend to overwrite just by selecting the containing tab.
This only occurs if I use the TextBox part to set the value, and not when I use the + and - buttons. Furthermore, taking the focus from the NumericUpDown prevents the bug from occurring. Hooking into the LostFocus event of the NumericUpDown, I could observe that the binding source of the respective binding had already changed to that within the view model associated with the next tab (instead of that associated with the NumericUpDown actually firing the LostFocus event), and that explains the surreal value bleed. (Additionally, it gets fired twice, as you will be able to see in the MWE.)
I could find a workaround by registering for the PreviewKeyDown event of the tab label and setting the focus to another element programmatically before the tab switch actually occurs. Still, as I said, this undermined my confidence in the WPF binding architecture. Is it a general WPF binding bug or is it specific to MahApps.Metro.Controls.NumericUpDown? What can I do to ensure such bugs do not occur in my code (apart from the amateur workaround I could come up with)?
The XAML part of the MWE:
<Window x:Class="NumericUpDownMWE.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:mahApps="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:local="clr-namespace:NumericUpDownMWE"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TabControl
ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<TextBlock Text="Value" />
<mahApps:NumericUpDown
LostFocus="HandleLostFocus"
Value="{Binding Value}" />
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
The respective code-behind:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace NumericUpDownMWE
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ExampleViewModel();
}
private void HandleLostFocus(object sender, RoutedEventArgs e)
{
FrameworkElement? frameworkElement = sender as FrameworkElement;
BindingExpression? bindingExpression = frameworkElement?.GetBindingExpression(MahApps.Metro.Controls.NumericUpDown.ValueProperty);
string? sourceName = (bindingExpression?.ResolvedSource as ExampleItem)?.Name;
}
}
public class ExampleItem : INotifyPropertyChanged
{
private int value;
public event PropertyChangedEventHandler? PropertyChanged;
public string Name { get; }
public int Value {
get { return value; }
set {
if (value != this.value) {
this.value = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
}
public ExampleItem(string name, int value)
{
this.Name = name;
this.value = value;
}
}
public class ExampleViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public ObservableCollection<ExampleItem> Items { get; }
public ExampleViewModel()
{
this.Items = new ObservableCollection<ExampleItem>() {
new ExampleItem("Item A", 1),
new ExampleItem("Item B", 2),
new ExampleItem("Item C", 3)
};
}
}
}
I have a UserControl consisting of a ComboBox with a Label. I am looking to update a screen with an instance of this ComboBox and dynamically create UserControls in a StackPanel, based on the SelectedItem value.
I currently have a screen with an instance of this ComboBox and have it binding the following way:
Pseudocode Example (removing unrelated code):
<!-- MyComboBoxExample.xaml -->
<ComboBox x:Name="myComboBox" SelectedValuePath="Key" DisplayMemberPath="Value" ItemsSource="{Binding MyBoxItems}/>
/* MyComboBoxExample.xaml.cs */
public static readonly DependencyProperty MyBoxItemsProperty = DependencyProperty.Register("MyBoxItems", typeof(Dictionary<string, string>),
typeof(MyComboBoxExample), new PropertyMetadata(null));
<!-- MyScreen.xaml -->
<local:MyComboBoxExample x:Name="MyComboBoxExampleInstance" MyBoxItems="{Binding Descriptions}"/>
I am new to WPF and databinding, so not sure the best way to implement this. Basically, on the screen: when MyComboBoxExampleInstance selection changes, dynamically set the controls of a StackPanel on the screen. I am not sure how to properly hook in to the SelectionChanged event of a child object of a UserControl.
Any thoughts, corrections, and (constructive) criticism is appreciated. Thanks for any help in advance.
There are several ways to go about this. Here's one way. It's not necessarily the best way but it's easy to understand.
First, the user control xaml. Note the binding of the ItemsSource property on the user control, which specifies MyComboBoxItems as the items source. More on where that comes from in a bit.
<UserControl x:Class="WpfApp1.MyUserControl"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ComboBox Height="Auto" ItemsSource="{Binding MyComboBoxItems}" SelectionChanged="OnSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
Now the code-behind, MyUserControl.xaml.cs. We provide an combobox selection changed event handler that in turn raises a custom event, MyComboBoxSelectionChanged, which is defined by the event argument class and delegate handler at the bottom of the code. Our OnSelectionChanged method simply forwards the selection change event via the custom event we've defined.
using System;
using System.Windows.Controls;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MyUserControl.xaml
/// </summary>
public partial class MyUserControl : UserControl
{
public event MyComboBoxSelectionChangedEventHandler MyComboBoxSelectionChanged;
public MyUserControl()
{
InitializeComponent();
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
MyComboBoxSelectionChanged?.Invoke(this,
new MyComboBoxSelectionChangedEventArgs() {MyComboBoxItem = e.AddedItems[0]});
}
}
}
public class MyComboBoxSelectionChangedEventArgs : EventArgs
{
public object MyComboBoxItem { get; set; }
}
public delegate void MyComboBoxSelectionChangedEventHandler(object sender, MyComboBoxSelectionChangedEventArgs e);
}
Now we go to our MainWindow.xaml, where we define an instance of MyUserControl and set a handler for the custom event we defined. We also provide a StackPanel to host the items that will be created on a selection changed event.
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<local:MyUserControl Width="140" Height="32" DataContext="{Binding}" Grid.Row="0" MyComboBoxSelectionChanged="OnSelectionChanged"></local:MyUserControl>
<StackPanel Grid.Row="1" x:Name="MyUserControls"/>
</Grid>
</Window>
Now the code-behind for MainWindow.xaml. Here we define a public property containing a list of objects of type MyComboBoxItem (defined at the bottom of the file), and we initialize the array with some values.
Recall that we set the ItemsSource property of the ComboBox inside MyUserControl to "{Binding MyComboBoxItems}", so the question is, how does the property defined in the MainWindow magically become available in MyUserControl?
In WPF, DataContext values are inherited from parent controls if they aren't explicitly set, and since we did not specify a data context for the control, the instance of MyUserControl inherits the DataContext of the parent window. In the constructor we set the MainWindow data context to refer to itself, so the MyComboBoxItems list is available to any child controls (and their children, and so on.)
Typically we'd go ahead and add a dependency property for the user control called ItemsSource and in the user control we'd bind the ComboBox's ItemsSource property to the dependency property rather than to MyComboxItems.
MainWindow.xaml would then bind it's collection directly to the dependency property on the user control. This helps make the user control more re-usable since it wouldn't depend on specific properties defined in an inherited data context.
Finally, in the event handler for the user control's custom event we obtain the value selected by the user and create a UserControl populated with a text box (all with various properties set to make the items interesting visually) and we directly add them to the Children property of the StackPanel.
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public List<MyComboBoxItem> MyComboBoxItems { get; set; } = new List<MyComboBoxItem>()
{
new MyComboBoxItem() {Text = "Item1"},
new MyComboBoxItem() {Text = "Item2"},
new MyComboBoxItem() {Text = "Item3"},
};
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void OnSelectionChanged(object sender, MyComboBoxSelectionChangedEventArgs e)
{
if (e.MyComboBoxItem is MyComboBoxItem item)
{
MyUserControls.Children.Add(
new UserControl()
{
Margin = new Thickness(2),
Background = new SolidColorBrush(Colors.LightGray),
Content = new TextBlock()
{
Margin = new Thickness(4),
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center,
FontSize = 48,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush(Colors.DarkGreen),
Text = item.Text
}
});
}
}
}
public class MyComboBoxItem
{
public string Text { get; set; }
}
}
Finally, I'd consider using an ItemsControl or a ListBox bound to an ObservableCollection rather than sticking things into a StackPanel. You could define a nice data template for the user control to display and maybe a DataTemplateSelector to use different user controls based on settings in the data item. This would allow me to simply add the reference to the MyComboBoxItem obtained in the selection changed handler to that collection, and the binding machinery would automatically generate a new item using the data template I defined and create the necessary visual elements to display it.
So given all that, here are the changes to do all that.
First, we modify our data item to add a color property. We'll use that property to determine how we display the selected item:
public class MyComboBoxItem
{
public string Color { get; set; }
public string Text { get; set; }
}
Now we implement INotifyPropertyChanged in MainWindow.xaml.cs to let the WPF binding engine update the UI when we change properties. This is the event handler and a helper method, OnPropertyChanged.
We also modify the combo box initializer to add a value for the Color property. We'll leave on blank for fun.
We then add a new ObservableCollect, "ActiveUserControls" to store the MyComboBoxItem received in the combo box selection changed event. We do that instead of creating user controls on the fly in code.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public List<MyComboBoxItem> MyComboBoxItems { get; set; } = new List<MyComboBoxItem>()
{
new MyComboBoxItem() {Text = "Item1", Color = "Red"},
new MyComboBoxItem() {Text = "Item2", Color = "Green"},
new MyComboBoxItem() {Text = "Item3"},
};
private ObservableCollection<MyComboBoxItem> _activeUserControls;
public ObservableCollection<MyComboBoxItem> ActiveUserControls
{
get => _activeUserControls;
set { _activeUserControls = value; OnPropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void OnSelectionChanged(object sender, MyComboBoxSelectionChangedEventArgs e)
{
if (e.MyComboBoxItem is MyComboBoxItem item)
{
if (ActiveUserControls == null)
{
ActiveUserControls = new ObservableCollection<MyComboBoxItem>();
}
ActiveUserControls.Add(item);
}
}
}
Now let's look at some changes we made to MyUserControl. We've modified the combo box ItemsSource to point at a property, ItemsSource defined in MyUserControl, and we also map the ItemTemplate to an ItemTemplate property in MyUserControl.
<UserControl x:Class="WpfApp1.MyUserControl"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid>
<ComboBox Height="Auto"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyUserControl}}}"
ItemTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyUserControl}}}"
SelectionChanged="OnSelectionChanged">
</ComboBox>
</Grid>
</UserControl>
Here's were we define those new properties in MyUserControl.cs.
public partial class MyUserControl : UserControl
{
public event MyComboBoxSelectionChangedEventHandler MyComboBoxSelectionChanged;
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource",
typeof(System.Collections.IEnumerable),
typeof(MyUserControl),
new PropertyMetadata(null));
public System.Collections.IEnumerable ItemsSource
{
get => GetValue(ItemsSourceProperty) as IEnumerable;
set => SetValue(ItemsSourceProperty, (IEnumerable)value);
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate",
typeof(DataTemplate),
typeof(MyUserControl),
new PropertyMetadata(null));
public DataTemplate ItemTemplate
{
get => GetValue(ItemTemplateProperty) as DataTemplate;
set => SetValue(ItemTemplateProperty, (DataTemplate)value);
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
MyComboBoxSelectionChanged?.Invoke(this,
new MyComboBoxSelectionChangedEventArgs() {MyComboBoxItem = e.AddedItems[0]});
}
}
}
Let's look at how we bind to those in MainWindow.xaml:
<local:MyUserControl Width="140"
Height="32"
Grid.Row="0"
MyComboBoxSelectionChanged="OnSelectionChanged"
ItemsSource="{Binding MyComboBoxItems}"
ItemTemplate="{StaticResource ComboBoxItemDataTemplate}" />
So now we can bind our items directly and provide our own data template to specify how the combobox should display the item.
Finally, I want to replace the StackPanel with an ItemsControl. This is like a ListBox without scrolling or item selection support. In fact, ListBox is derived from ItemsControl. I also want to use a different user control in the list based on the value of the Color property. To do that, we define some data templates for each value in MainWindow.Xaml:
<DataTemplate x:Key="ComboBoxItemDataTemplate"
DataType="local:MyComboBoxItem">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="4"
Text="{Binding Text}" />
<TextBlock Margin="4"
Text="{Binding Color}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="GreenUserControlDataTemplate"
DataType="local:MyComboBoxItem">
<local:GreenUserControl DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="RedUserControlDataTemplate"
DataType="local:MyComboBoxItem">
<local:RedUserControl DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="UnspecifiedUserControlDataTemplate"
DataType="local:MyComboBoxItem">
<TextBlock Margin="4"
Text="{Binding Text}" />
</DataTemplate>
Here's RedUserControl. Green is the same with a different foreground color.
<UserControl x:Class="WpfApp1.RedUserControl"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid Background="LightGray"
Margin="2">
<TextBlock Margin="4"
Foreground="DarkRed"
TextWrapping="Wrap"
Text="{Binding Text}"
FontSize="24"
FontWeight="Bold" />
</Grid>
</UserControl>
Now the trick is to use the right data template based on the color value. For that we create a DataTemplateSelector. This is called by WPF for each item to be displayed. We can examine the data context object a choose which data template to use:
public class UserControlDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (container is FrameworkElement fe)
{
if (item is MyComboBoxItem cbItem)
{
if (cbItem.Color == "Red")
{
return fe.FindResource("RedUserControlDataTemplate") as DataTemplate;
}
if (cbItem.Color == "Green")
{
return fe.FindResource("GreenUserControlDataTemplate") as DataTemplate;
}
return fe.FindResource("UnspecifiedUserControlDataTemplate") as DataTemplate;
}
}
return null;
}
}
We create an instance of our data template selector in xaml in MainWindow.xaml:
<Window.Resources>
<local:UserControlDataTemplateSelector x:Key="UserControlDataTemplateSelector" />
...
Finally we replace our stack panel with an Items control:
<ItemsControl Grid.Row="1"
x:Name="MyUserControls"
ItemsSource="{Binding ActiveUserControls}"
ItemTemplateSelector="{StaticResource UserControlDataTemplateSelector}" />
I'm simply trying to create a binding for a UserControl within a UserControl using WPF/Caliburn, but I am having trouble properly binding the nested UserControl.
The basic layout is a ShellViewModel : Conductor, and within the ShellView there is a ContentControl that is populated by the ShellViewModel.ActivateItem method that loads a UserControl (PageViewModel), and within the PageView UserControl there is a nested UserControl called "SimpleControl".
The binding works when the page loads (it displays the "Initial Text Value" string in the nested UserControl), but it does not appear to be bound to the PropertyChanged event on PageView (and never updates its value when the test button is pressed). The Label in the Parent UserControl (PageView) is bound correctly/updates as expected.
PageView.xaml:
<UserControl x:Class="CaliburnTest.Views.PageView"
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:CaliburnTest.Views"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Background="Aqua">
<Label Content="{Binding TextLabelTest, FallbackValue=DEFAULT}"></Label>
<local:SimpleControl cal:Bind.Model="WPFCaliburnTemplate.Views.PageView" TextValue="{Binding TextLabelTest}"></local:SimpleControl>
<Button Name="UpdateTextButton">Update Text</Button>
</StackPanel>
</UserControl>
PageViewModel.cs:
using Caliburn.Micro;
namespace CaliburnTest.ViewModels
{
public class PageViewModel : Screen
{
private string _textLabelTest;
public string TextLabelTest
{
get { return _textLabelTest; }
set
{
_textLabelTest = value;
NotifyOfPropertyChange(() => TextLabelTest);
}
}
public PageViewModel()
{
TextLabelTest = "Initial Text Value";
}
public void UpdateTextButton()
{
TextLabelTest = "Updated Text Value";
}
}
}
SimpleControl.xaml:
<UserControl x:Class="CaliburnTest.Views.SimpleControl"
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:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro.Platform.Core"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border Margin="10" BorderThickness="1" BorderBrush="#FF9A9A9A">
<StackPanel>
<Label Name="TextLabel"></Label>
</StackPanel>
</Border>
</UserControl>
And finally SimpleControl.xaml.cs:
using System.Windows;
using System.Windows.Controls;
namespace CaliburnTest.Views
{
public partial class SimpleControl : UserControl
{
public SimpleControl()
{
InitializeComponent();
}
public static DependencyProperty TextValueProperty = DependencyProperty.Register("TextValue", typeof(string), typeof(SimpleControl),
new FrameworkPropertyMetadata("", TextValueChangedCallBack));
public string TextValue
{
get { return (string)GetValue(TextValueProperty); }
set
{
SetValue(TextValueProperty, value);
Refresh();
}
}
protected static void TextValueChangedCallBack(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as SimpleControl).TextValue = (string)e.NewValue;
}
private void Refresh()
{
TextLabel.Content = TextValue;
}
}
}
I have been banging on this all day and cannot figure out what I'm doing wrong. I've tried all combinations of different DataContexts, RelativeSources, and whatever else I can find on SO and Google but am still coming up short. I have a much more complicated custom UserControl I'm trying to work with but I created this simple example to try and figure out the issue.
I played around with all sorts of combinations and I believe I found a result that appears to work, though I'm not entirely sure why:
First, I changed the way I was binding the "TextValue" dependency property to:
<local:SimpleControl TextValue="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}, Path=DataContext.TextLabelTest}" />
However, this was still only working for the initial value and the binding was not updating when the string was updated. In the end, I had to edit SimpleControl.xaml.cs like so:
protected static void TextValueChangedCallBack(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// (sender as SimpleControl).TextValue = (string)e.NewValue;
(sender as SimpleControl).Refresh();
}
As you can see, I commented out the line setting the SimpleControl.TextValue (and in turn setting the TextValueProperty DependencyProperty) and simply called the SimpleControl.Refresh() method to update the label. I'm not sure why setting the TextValueProperty was breaking the DependencyProperty (even though it was already set when the string updated), but I think I need to go brush up on some MSDN articles!
I have a TextBox in a TabControl. If I edit the text in the box and then switch to another tab, the text is lost. If I change focus (via TAB key on keyboard) and then switch to another tab, the new text is set in my viewmodel.
Here is my code:
<Window x:Class="TabSwitchProblem.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">
<TabControl ItemsSource="{Binding Pages}">
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox Text="{Binding PageContent}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
public partial class MainWindow : Window
{
public ObservableCollection<PageViewModel> Pages
{
get { return (ObservableCollection<PageViewModel>)GetValue(PagesProperty); }
set { SetValue(PagesProperty, value); }
}
public static readonly DependencyProperty PagesProperty =
DependencyProperty.Register("Pages", typeof(ObservableCollection<PageViewModel>), typeof(MainWindow), new PropertyMetadata(null));
public MainWindow()
{
InitializeComponent();
Pages = new ObservableCollection<PageViewModel>();
Pages.Add(new PageViewModel());
Pages.Add(new PageViewModel());
DataContext = this;
}
}
public class PageViewModel : DependencyObject
{
public string PageContent
{
get { return (string)GetValue(PageContentProperty); }
set { SetValue(PageContentProperty, value); }
}
public static readonly DependencyProperty PageContentProperty =
DependencyProperty.Register("PageContent", typeof(string), typeof(PageViewModel), new PropertyMetadata(null));
}
How can I be sure to get the text updated in my viewmodel?
You may need to add UpdateSourceTrigger=LostFocus to the <TextBox Text="{Binding PageContent}" /> line.
Code should look like this
<TextBox Text="{Binding PageContent, UpdateSourceTrigger=LostFocus}" />
That should work.
You should set the UpdateSourceTrigger to PropertyChanged if you want your binding to update the target every time the value changes. By default the UpdateSourceTrigger for Text property of a TextBox is LostFocus, which updates the target only after the focus is lost.
<TextBox Text="{Binding PageContent, UpdateSourceTrigger=PropertyChanged}" />
The previously accepted answer, although it works, involves changing the binding behavior of the textbox to UpdatesourceTrigger=PropertyChanged. This may not be acceptable for some usages of textbox or other input-accepting controls.
A simple fix for this is to manually set focus to another element on your control (or the tabcontrol itself) in code-behind on SelectionChanged of your TabControl. That way the currently focused input element actually loses focus, triggering the binding:
<TabControl x:Name="MyTabControl" SelectionChanged="MyTabControl_OnSelectionChanged">
private void MyTabControl_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
MyTabControl.Focus();
}
While it is trivial to store a checkbox's checked state in a variable using the checkbox's Click event, how would I do it via databinding? All the examples I have found have the UI updated from some datasource, or bind one control to another; I want to update a member variable when the checkbox is clicked.
TIA for any pointers...
You must make your binding bidirectional :
<checkbox IsChecked="{Binding Path=MyProperty, Mode=TwoWay}"/>
You need a dependency property for this:
public BindingList<User> Users
{
get { return (BindingList<User>)GetValue(UsersProperty); }
set { SetValue(UsersProperty, value); }
}
public static readonly DependencyProperty UsersProperty =
DependencyProperty.Register("Users", typeof(BindingList<User>),
typeof(OptionsDialog));
Once that is done, you bind the checkbox to the dependency property:
<CheckBox x:Name="myCheckBox"
IsChecked="{Binding ElementName=window1, Path=CheckBoxIsChecked}" />
For that to work you have to name your Window or UserControl in its openning tag, and use that name in the ElementName parameter.
With this code, whenever you change the property on the code side, you will change the textbox. Also, whenever you check/uncheck the textbox, the Dependency Property will change too.
EDIT:
An easy way to create a dependency property is typing the snippet propdp, which will give you the general code for Dependency Properties.
All the code:
XAML:
<Window x:Class="StackOverflowTests.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" x:Name="window1" Height="300" Width="300">
<Grid>
<StackPanel Orientation="Vertical">
<CheckBox Margin="10"
x:Name="myCheckBox"
IsChecked="{Binding ElementName=window1, Path=IsCheckBoxChecked}">
Bound CheckBox
</CheckBox>
<Label Content="{Binding ElementName=window1, Path=IsCheckBoxChecked}"
ContentStringFormat="Is checkbox checked? {0}" />
</StackPanel>
</Grid>
</Window>
C#:
using System.Windows;
namespace StackOverflowTests
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public bool IsCheckBoxChecked
{
get { return (bool)GetValue(IsCheckBoxCheckedProperty); }
set { SetValue(IsCheckBoxCheckedProperty, value); }
}
// Using a DependencyProperty as the backing store for
//IsCheckBoxChecked. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsCheckBoxCheckedProperty =
DependencyProperty.Register("IsCheckBoxChecked", typeof(bool),
typeof(Window1), new UIPropertyMetadata(false));
public Window1()
{
InitializeComponent();
}
}
}
Notice how the only code behind is the Dependency Property. Both the label and the checkbox are bound to it. If the checkbox changes, the label changes too.
Hello this is my first time posting so please be patient:
my answer was to create a simple property:
public bool Checked { get; set; }
Then to set the data context of the Checkbox (called cb1):
cb1.DataContext = this;
Then to bind the IsChecked proerty of it in the xaml
IsChecked="{Binding Checked}"
The code is like this:
XAML
<CheckBox x:Name="cb1"
HorizontalAlignment="Left"
Margin="439,81,0,0"
VerticalAlignment="Top"
Height="35" Width="96"
IsChecked="{Binding Checked}"/>
Code behind
public partial class MainWindow : Window
{
public bool Checked { get; set; }
public MainWindow()
{
InitializeComponent();
cb1.DataContext = this;
}
private void myyButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Checked.ToString());
}
}
Should be easier than that. Just use:
<Checkbox IsChecked="{Binding Path=myVar, UpdateSourceTrigger=PropertyChanged}" />
if you have the property "MyProperty" on your data-class, then you bind the IsChecked like this.... (the converter is optional, but sometimes you need that)
<Window.Resources>
<local:MyBoolConverter x:Key="MyBoolConverterKey"/>
</Window.Resources>
<checkbox IsChecked="{Binding Path=MyProperty, Converter={StaticResource MyBoolConverterKey}}"/>
This works for me (essential code only included, fill more for your needs):
In XAML a user control is defined:
<UserControl x:Class="Mockup.TestTab" ......>
<!-- a checkbox somewhere within the control -->
<!-- IsChecked is bound to Property C1 of the DataContext -->
<CheckBox Content="CheckBox 1" IsChecked="{Binding C1, Mode=TwoWay}" />
</UserControl>
In code behind for UserControl
public partial class TestTab : UserControl
{
public TestTab()
{
InitializeComponent(); // the standard bit
// then we set the DataContex of TestTab Control to a MyViewModel object
// this MyViewModel object becomes the DataContext for all controls
// within TestTab ... including our CheckBox
DataContext = new MyViewModel(....);
}
}
Somewhere in solution class MyViewModel is defined
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool m_c1 = true;
public bool C1 {
get { return m_c1; }
set {
if (m_c1 != value) {
m_c1 = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("C1"));
}
}
}
}
No backend and ViewModel Code:
I made such check box to control other control's visibility.
<CheckBox x:Name="rulerCheckbox" Content="Is Ruler Visible" IsChecked="True"/>
and in the other control, I added such binding:
Visibility="{Binding IsChecked, ElementName=rulerCheckbox, Mode=TwoWay, Converter={StaticResource BoolVisConverter}}">