Im trying to bind 2 different WPF controls to the same property in the ViewModel, a CheckBox.IsChecked and an Expander.IsExpanded. The behavior I want to achieve is to have the CheckBox affect the ViewModel (and therefore the Expander as well), but not the other way bound.
Something like:
Checkbox Checked -> ViewModel property set to frue -> Expander.Expand
Checkbox Unchecked -> ViewModel property set to false -> Expander.Collapse
Expander Expanded -> Nothing else affected
Expander Collapsed -> Nothing else affected
Here's the XAML:
<Window x:Class="WpfApplication9.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">
<Expander IsExpanded="{Binding IsChecked, Mode=OneWay}">
<Expander.Header>
<CheckBox IsChecked="{Binding IsChecked}" Content="Is Checked"/>
</Expander.Header>
<TextBlock Text="Expanded!"/>
</Expander>
</Window>
and the Code:
using System.ComponentModel;
using System.Windows;
namespace WpfApplication9
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel: INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
NotifyPropertyChange("IsChecked");
}
}
protected void NotifyPropertyChange(string PropertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
Now my problem is, as soon as I click on the Expander to expand / collapse it, the Binding seems to stop working. Can anyone explain to me why this is happening and how do I achieve this? Thanks in advance!
New Answer
Discovered you could do this by setting your UpdateSourceTrigger to Explicit on your Expander. This keeps the binding as Two-Way, but never updates the Source since you're telling it not to update the source unless you explicitly tell it to.
<Expander IsExpanded="{Binding IsChecked, UpdateSourceTrigger=Explicit}">
<Expander.Header>
<CheckBox IsChecked="{Binding IsChecked}" Content="Is Checked"/>
</Expander.Header>
<TextBlock Text="Expanded!"/>
</Expander>
Leaving my old answer below so the comments make sense, and because I still feel there is no problem with view-specific code going in the code-behind of a view :)
Old Answer
Personally since this is View-Specific code, I see no problem with using a CheckBox click event to set the Expander's IsExpanded value.
private void MyCheckBox_Click(object sender, RoutedEventArgs e)
{
MyExpander.IsExpanded = ((CheckBox)sender).IsChecked.GetValueOrDefault();
}
You could make this even more generic by removing the names and navigating the Visual Tree to find the Expander associated with the CheckBox. Here's an example using some Visual Tree Helpers I built
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
var chk = (CheckBox)sender;
var expander = VisualTreeHelpers.FindAncestor<Expander>(chk);
if (expander != null)
expander.IsExpanded = chk.IsChecked.GetValueOrDefault();
}
If you want the checkbox to affect the expander (but not vice versa) then bind the expander normally and use OneWayToSource on the checkbox:
<Window x:Class="WpfApplication9.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">
<Expander IsExpanded="{Binding IsChecked}">
<Expander.Header>
<CheckBox IsChecked="{Binding IsChecked, Mode=OneWayToSource}" Content="Is Checked"/>
</Expander.Header>
<TextBlock Text="Expanded!"/>
</Expander>
</Window>
Using OneWayToSource on the checkbox will allow it to:
modify the underlying property (and therefore affect the expander, which is also bound to that property)
not be affected by other components that make changes to the underlying property
If you'd like to avoid any code-behind, you can add a degree of separation between the Expander and CheckBox states in your ViewModel:
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
NotifyPropertyChange("IsChecked");
IsExpanded = value;
}
}
private bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
_isExpanded = value;
NotifyPropertyChange("IsExpanded");
}
}
<Expander IsExpanded="{Binding IsExpanded}">
<Expander.Header>
<CheckBox IsChecked="{Binding IsChecked}" Content="Is Checked" x:Name="cb"/>
</Expander.Header>
<TextBlock Text="Expanded!"/>
</Expander>
Related
I have an application where the main window contains a user control, and inside that user control are items stored in an ItemsControl. Each item can be removed by clicking an 'x' button.
The problem I am facing is that although the Keyboard focus is initially set to the user control, when you remove an item, focus is then transferred to the main window, instead of back to the user control?
Is there a way I can fix this without having to add code behind to manually store/retrieve/set focus after the click?
I have lots of these buttons within my application and I'm trying to avoid having to add code all over the place to manage returning the Focus.
I have created a very simple example to show the issue :
<UserControl x:Class="WpfApp28.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Width="300">
<StackPanel>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding}" />
<Button Content="x"
Width="20"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="Button_Click" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
Focusable = true;
Loaded += MyControl_Loaded;
}
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
Keyboard.Focus(this);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement fe && fe.DataContext is string item)
{
(DataContext as ObservableCollection<string>).Remove(item);
}
}
}
<Window x:Class="WpfApp28.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp28"
Title="MainWindow" Height="450" Width="800">
<local:MyControl DataContext="{Binding Items}" />
public partial class MainWindow : Window
{
public ObservableCollection<string> Items { get; } = new ObservableCollection<string>();
public MainWindow()
{
Items.Add("hello");
Items.Add("there");
Items.Add("world");
DataContext = this;
InitializeComponent();
DispatcherTimer t = new DispatcherTimer();
t.Interval = TimeSpan.FromMilliseconds(250);
t.Tick += T_Tick;
t.Start();
}
private void T_Tick(object? sender, EventArgs e)
{
Title = Keyboard.FocusedElement?.GetType().ToString() ?? "NULL";
}
}
The reason that the keyboard focus moves to the hosting Window is obvious once you understand how WPF handles focus. It's important to know that WPF uses scopes in which the focus traverses the elements.
There can be multiple focus scopes allowing multiple elements to remain focused simultaneously.
By default, the hosting Window defines a focus scope. Since it is the only focus scope, it is global (the scope of the complete visual tree).
What happens in your code in short:
The Button receives the focus via mouse click
The click handler removes the clicked item and therefore the clicked Button from the visual tree
WPF moves focus back to the focus scope root, which is the MainWindow in your case
You have multiple options to prevent the focus from being moved back to the focus root. Some involve code-behind.
The following examples show how to move the focus back to the parent UserControl. But it could be any element as well:
You can configure the Button (the element that "steals" the current focus) to be not focusable. This only works if the UserControl is already focused:
<DataTemplate>
<Button Content="x"
Focusable="False" />
</DataTemplate>
You can introduce a new focus scope. Since you want the UserControl itself to be focused, you must choose the root element of the UserControl. You can achieve this by using the FocusManager helper class:
<UserControl>
<Grid x:Name="RootPanel"
FocusManager.IsFocusScope="True"
Width="300">
</Grid>
</UserControl>
You can of course register a Button.Click handler or preferably a routed command to move the focus back to the UserControl explicitly. A routed command can be more convenient in most cases. It allows to send a command parameter that makes the code-behind simpler.
Note, since Button.Click is a routed event, you can simply register a Button.Click event handler on the UserControl. This example uses the existing click handler that is used to remove the item from the ItemsControl:
UserControl.xaml
<DataTemplate>
<Button Content="x"
Click="OnButtonClick" />
</DataTemplate>
UserControl.xaml.cs
private void OnButtonClick(object sender, RoutedEventArgs)
{
/* Delete the item */
Keyboard.Focus(this);
}
Final suggested solution
To improve your code and handling of the UserControl you must definitely implement an ItemsSource dependency property and use a routed command to delete the items.
The following example uses the predefined ApplicationCommands.Delete routed command. You will notice how simple the code has become:
MyControl.xaml.cs
public partial class MyControl : UserControl
{
public IList ItemsSource
{
get => (IList)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource",
typeof(IList),
typeof(UserControl4), new PropertyMetadata(default));
public MyControl()
{
InitializeComponent();
this.Focusable = true;
}
public override void OnApplyTemplate()
=> Keyboard.Focus(this);
private void DeleteItemCommand_Executed(object sender, ExecutedRoutedEventArgs e)
=> this.ItemsSource.Remove(e.Parameter);
private void DeleteItemCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = this.ItemsSource.Contains(e.Parameter);
}
MyControl.xaml
<UserControl>
<UserControl.CommandBindings>
<CommandBinding Command="{x:Static ApplicationCommands.Delete}"
Executed="DeleteItemCommand_Executed"
CanExecute="DeleteItemCommand_CanExecute" />
</UserControl.CommandBindings>
<Grid x:Name="RootPanel"
FocusManager.IsFocusScope="True">
<StackPanel>
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:UserControl4}, Path=ItemsSource}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding}" />
<Button Content="x"
Command="{x:Static ApplicationCommands.Delete}"
CommandParameter="{Binding}"
Width="20"
HorizontalAlignment="Right"
VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
MainWindow.xaml
<Window>
<MyControl ItemsSource="{Binding Items}" />
</Window>
Remarks
You should consider to use a ListBox instead of the pure ItemsControl.
ListBox is an extended ItemsControl. It will significantly improve performance and provides a ScrollViewer by default.
I have a custom user control called GoalProgressControl. Another user control contains GoalProgressControl and sets its GoalName attribute via databinding in XAML. However, the GoalName property is never set. When I check it in debug mode GoalName remains "null" for the control's lifetime.
How do I set the GoalName property? Is there something I am doing incorrectly?
I am using .NET Framework 4 and Silverlight 4. I am relatively new to XAML and Silverlight so any help would be greatly appreciated.
I have attempted to change GoalProgressControl.GoalName into a POCO property but this causes a Silverlight error, and my reading leads me to believe that databound properties should be of type DependencyProperty. I've also simplified my code to just focus on the GoalName property (the code is below) with no success.
Here is GoalProgressControl.xaml:
<UserControl x:Class="GoalView.GoalProgressControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Height="100">
<Border Margin="5" Padding="5" BorderBrush="#999" BorderThickness="1">
<TextBlock Text="{Binding GoalName}"/>
</Border>
</UserControl>
GoalProgressControl.xaml.cs:
public partial class GoalProgressControl : UserControl, INotifyPropertyChanged
{
public GoalProgressControl()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public static DependencyProperty GoalNameProperty = DependencyProperty.Register("GoalName", typeof(string), typeof(GoalProgressControl), null);
public string GoalName
{
get
{
return (String)GetValue(GoalProgressControl.GoalNameProperty);
}
set
{
base.SetValue(GoalProgressControl.GoalNameProperty, value);
NotifyPropertyChanged("GoalName");
}
}
}
I've placed GoalProgressControl on another page:
<Grid Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="5" Background="#eee" Height="200">
<Border BorderBrush="#999" BorderThickness="1" Background="White">
<StackPanel>
<hgc:SectionTitleBar x:Name="ttlGoals" Title="Personal Goals" ImageSource="../Images/check.png" Uri="/Pages/GoalPage.xaml" MoreVisibility="Visible" />
<ItemsControl ItemsSource="{Binding Path=GoalItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--TextBlock Text="{Binding Path=[Name]}"/-->
<goal:GoalProgressControl GoalName="{Binding Path=[Name]}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
</Grid>
Please note the commented out "TextBlock" item above. If I comment in the TextBlock and comment out the GoalProgressControl, the binding works correctly and the TextBlock shows the GoalName correctly. Also, if I replace the "GoalName" property above with a simple text string (ex "hello world"), the control renders correctly and "hello world" is shown on the control when it renders.
You have to pass
new PropertyMetadata(OnValueChanged)
as a last parameter to DependencyProperty.Register call and set the property
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((GoalProgressControl)d).GoalName = (String)e.NewValue;
}
I've written an article on Silverlight 4 Custom Property Binding that you might find useful, and it can be found here:
http://nick-howard.blogspot.com/2011/03/silverlight-4-custom-property-binding.html
What is the proper way to implement Custom Properties in Silverlight UserControls?
Every "Page" in Silverlight is technically a UserControl (they are derived from the UserControl class). When I say UserControl here, I mean a Custom UserControl that will be used inside many different pages in many different scenarios (similar to an ASP.NET UserControl).
I would like the Custom UserControl to support Binding and not rely on the Name of the Property it is binding to, to always be the same. Instead, I would like the UserControl itself to have a property that the Controls inside the UserControl bind to, and the ViewModels outside the UserControl also bind to. (please see the example below)
Binding within the UserControl works, Binding within the MainPage works, The Binding I set up between the MainPage and the UserControl does not work. Specifically this line:
<myUserControls:MyCustomUserControl x:Name="MyCustomControl2"
SelectedText="{Binding MainPageSelectedText, Mode=TwoWay}"
Width="200" Height="50" />
example output:
MainPage.xaml
<UserControl x:Class="SilverlightCustomUserControl.MainPage"
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:myUserControls="clr-namespace:SilverlightCustomUserControl"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Canvas x:Name="LayoutRoot">
<StackPanel Orientation="Vertical">
<TextBlock Text="UserControl Binding:" Width="200"></TextBlock>
<myUserControls:MyCustomUserControl x:Name="MyCustomControl2" SelectedText="{Binding MainPageSelectedText, Mode=TwoWay}" Width="200" Height="50" />
<TextBlock Text="MainPage Binding:" Width="200"></TextBlock>
<TextBox Text="{Binding MainPageSelectedText, Mode=TwoWay}" Width="200"></TextBox>
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding MainPageSelectedText}" Width="200" Height="24"></TextBlock>
</Border>
</StackPanel>
</Canvas>
</UserControl>
MainPage.xaml.cs
namespace SilverlightCustomUserControl
{
public partial class MainPage : UserControl, INotifyPropertyChanged
{
//NOTE: would probably be in a ViewModel
public string MainPageSelectedText
{
get { return _MainPageSelectedText; }
set
{
string myValue = value ?? String.Empty;
if (_MainPageSelectedText != myValue)
{
_MainPageSelectedText = value;
OnPropertyChanged("MainPageSelectedText");
}
}
}
private string _MainPageSelectedText;
public MainPage()
{
InitializeComponent();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
PropertyChangedEventHandler ph = this.PropertyChanged;
if (ph != null)
ph(this, new PropertyChangedEventArgs(name));
}
#endregion
}
}
MyCustomUserControl.xaml
<UserControl
x:Class="SilverlightCustomUserControl.MyCustomUserControl"
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"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<StackPanel>
<TextBox Text="{Binding SelectedText, Mode=TwoWay}" />
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding SelectedText}" Height="24"></TextBlock>
</Border>
</StackPanel>
</Grid>
</UserControl>
MyCustomUserControl.xaml.cs
namespace SilverlightCustomUserControl
{
public partial class MyCustomUserControl : UserControl
{
public string SelectedText
{
get { return (string)GetValue(SelectedTextProperty); }
set { SetValue(SelectedTextProperty, value); }
}
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.Register("SelectedText", typeof(string), typeof(MyCustomUserControl), new PropertyMetadata("", SelectedText_PropertyChangedCallback));
public MyCustomUserControl()
{
InitializeComponent();
}
private static void SelectedText_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//empty
}
}
}
References (how I got this far):
use DependencyPropertys:
http://geekswithblogs.net/thibbard/archive/2008/04/22/wpf-custom-control-dependency-property-gotcha.aspx
use DependencyPropertys, add x:Name to your UserControl - add Binding with ElementName, set Custom property again in the PropertyChangedCallback method:
Setting Custom Properties in UserControl via DataBinding
don't use custom properties, rely on underlying datacontext names (I do not like this solution):
wpf trouble using dependency properties in a UserControl
I understand it as the reason your control is not receiving the new value from the maim page is that you are setting the DataContext of the control. If you hadn't then the control's DataContext will be inherited from its parent, the main page in this case.
To get this to work I removed you control's DataContext setting, added an x:Name to each control and set the binding in the constructor of the control using the [name].SetBinding method.
I did the binding in the ctor as I couldn't figure out a way of setting the Source property of the declarative binding in the xaml to Self. i.e. {Binding SelectedText, Mode=TwoWay, Source=[Self here some how]}. I did try using RelativeSource={RelativeSource Self} with no joy.
NOTE: All this is SL3.
The Issue was the UserControl was throwing a DataBinding error (visible in the Output window while debugging)
Because The UserControl's DataContext was set to "Self" in its own xaml, it was looking for the MainPageSelectedText within its own context (it was not looking for the MainPageSelectedText within the "MainPage" which is where you might think it would look, because when you are physically writing/looking at the code that is what is in "context")
I was able to get this "working" by setting the Binding in the code behind. Setting the binding in the code behind is the only way to set the UserControl itself as the "Source" of the binding. But this only works if the Binding is TwoWay. OneWay binding will break this code. A better solution altogether would be to create a Silverlight Control, not a UserControl.
See Also:
http://social.msdn.microsoft.com/Forums/en-US/silverlightcontrols/thread/052a2b67-20fc-4f6a-84db-07c85ceb3303
http://msdn.microsoft.com/en-us/library/cc278064%28VS.95%29.aspx
MyCustomUserControl.xaml
<UserControl
x:Class="SilverlightCustomUserControl.MyCustomUserControl"
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">
<Grid>
<StackPanel>
<TextBox x:Name="UserControlTextBox" />
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock x:Name="UserControlTextBlock" Height="24"></TextBlock>
</Border>
</StackPanel>
</Grid>
</UserControl>
MyCustomUserControl.xaml.cs
namespace SilverlightCustomUserControl
{
public partial class MyCustomUserControl : UserControl
{
public string SelectedText
{
get { return (string)GetValue(SelectedTextProperty); }
set { SetValue(SelectedTextProperty, value); }
}
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.Register("SelectedText", typeof(string), typeof(MyCustomUserControl), new PropertyMetadata("", SelectedText_PropertyChangedCallback));
public MyCustomUserControl()
{
InitializeComponent();
//SEE HERE
UserControlTextBox.SetBinding(TextBox.TextProperty, new Binding() { Source = this, Path = new PropertyPath("SelectedText"), Mode = BindingMode.TwoWay });
UserControlTextBlock.SetBinding(TextBlock.TextProperty, new Binding() { Source = this, Path = new PropertyPath("SelectedText") });
//SEE HERE
}
private static void SelectedText_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//empty
}
}
}
Instead of binding data context to self, you can set the binding in xaml by adding an x:Name for the user control and then binding in the user control xaml follows:
<UserControl
x:Class="SilverlightCustomUserControl.MyCustomUserControl"
x:Name="myUserControl
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">
<Grid>
<StackPanel>
<TextBox Text="{Binding SelectedText, ElementName=myUserContol, Mode=TwoWay}" />
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding SelectedText,ElementName=myUserControl}" Height="24"></TextBlock>
</Border>
</StackPanel>
</Grid>
</UserControl>
I have this XAML:
<Window x:Class="WpfBindToCodeBehind.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"
Loaded="Window_Loaded">
<StackPanel Orientation="Vertical">
<Button Name="ToggleExpand" Click="ToggleExpand_Click">Toggle Expander</Button>
<Expander Name="Expander"
Header="Don't click me, click the button!"
IsExpanded="{Binding RelativeSource={RelativeSource Self},Path=MayExpand}">
<TextBlock Text="{Binding}"/>
</Expander>
</StackPanel>
</Window>
This is the code behind:
public partial class Window1 : Window,INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Window1()
{
InitializeComponent();
}
private void ToggleExpand_Click(object sender, RoutedEventArgs e)
{
MayExpand = !mayExpand;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Expander.DataContext = "Show me";
}
private bool mayExpand;
public bool MayExpand
{
get { return mayExpand; }
set
{
mayExpand = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("MayExpand"));
}
}
}
The Binding expression for the IsExpanded property is not working. This code is a simplification, in reality the expander's binding is already set through the datacontent of a parent control.
How can I bind the IsExpanded property to a property of the code behind?
Can I bind it to the result of a method in the code behind?
The source for the binding is a RelativeSource.Self. That means the source is the Expander rather than the Window. Something like this will work:
IsExpanded="{Binding MayExpand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}"
You could also use a name to simplify things:
<Window x:Name="_root">
<Expander IsExpanded="{Binding MayExpand, ElementName=_root}"/>
This should be a very simple case, but I am pulling hair trying to get it to work. Here is the setup:
I am designing an app that will have an read-only mode and edit mode for some data. So I created a User Control which is a textbox and textblock bound to the same text data and are conditionally visible based on EditableMode property (so when it's editable the textbox is shown and when it's not the textblock is shown)
Now, I want to have many of these controls in my main window and have them all bound too a single bool property. When that property is changed via a button, I want all TextBlocks to turn into TextBoxes or back.
My problem is that the control is set correctly on binding, and if I do myUserControl.Editable = true. But it doesn't change if bind it to a bool property.
Here is the code for my user control:
<UserControl x:Class="CustomerCareTool.Controls.EditableLabelControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:CustomerCareTool.Converters"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<UserControl.Resources>
<src:BoolToVisibility x:Key="boolToVisibility" Inverted="False" />
<src:BoolToVisibility x:Key="invertedBoolToVisibility" Inverted="True" />
</UserControl.Resources>
<Grid>
<TextBlock Name="textBlock" Text="{Binding Path=TextBoxValue}" Visibility="{Binding Path=EditableMode, Converter={StaticResource invertedBoolToVisibility}}"/>
<TextBox Name="textBox" Visibility="{Binding Path=EditableMode, Converter={StaticResource boolToVisibility}}">
<TextBox.Text>
<Binding Path="TextBoxValue" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
</Grid>
I used a converter to convert bool to visibility and inverse bool to visibility. Not sure if that's at all needed here.
And this is the code behind:
public partial class EditableLabelControl : UserControl
{
public EditableLabelControl()
{
InitializeComponent();
}
public string TextBoxValue
{
get { return (string)GetValue(TextBoxValueProperty); }
set { SetValue(TextBoxValueProperty, value); }
}
public static readonly DependencyProperty TextBoxValueProperty =
DependencyProperty.Register("TextBoxValue", typeof(string), typeof(EditableLabelControl), new UIPropertyMetadata());
public bool EditableMode
{
get { return (bool)GetValue(EditableModeProperty); }
set { SetValue(EditableModeProperty, value); }
}
public static readonly DependencyProperty EditableModeProperty =
DependencyProperty.Register("EditableMode", typeof(bool),typeof(EditableLabelControl), new UIPropertyMetadata(false, EditableModePropertyCallBack));
static void EditableModePropertyCallBack(DependencyObject property,
DependencyPropertyChangedEventArgs args)
{
var editableLabelControl = (EditableLabelControl)property;
var editMode = (bool)args.NewValue;
if (editMode)
{
editableLabelControl.textBox.Visibility = Visibility.Visible;
editableLabelControl.textBlock.Visibility = Visibility.Collapsed;
}
else
{
editableLabelControl.textBox.Visibility = Visibility.Collapsed;
editableLabelControl.textBlock.Visibility = Visibility.Visible;
}
}
}
Now in my main application I have the control added like this:
<Controls:EditableLabelControl x:Name="testCtrl" EditableMode="{Binding Path=Editable}" TextBoxValue="John Smith" Grid.Row="0"/>
For that same application the DataContext is set to self
DataContext="{Binding RelativeSource={RelativeSource Self}}"
And the code behind looks like this:
public partial class OrderInfoView : Window, INotifyPropertyChanged
{
public OrderInfoView()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Editable = !Editable;
}
private bool _editable = false;
public bool Editable
{
get
{
return _editable;
}
set
{
_editable = value;
OnPropertyChanged("Editable");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Clicking the button doesn't do anything :( I tried everything to get this to work, and no dice. Would really appreciate some help!
I tried the following, and still does not work:
public bool Editable
{
get { return (bool)GetValue(EditableProperty); }
set { SetValue(EditableProperty, value); }
}
public static readonly DependencyProperty EditableProperty =
DependencyProperty.Register("Editable", typeof(bool), typeof(OrderInfoView), new UIPropertyMetadata(false));
It looks like your solution may be more complex than necessary. If all you want to do is have a disabled TextBox look like a TextBlock then you can do this using a trigger and a template. Then you can apply that style to all text boxes.
Here's an example of that approach:
<Window x:Class="WpfApplication25.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"
>
<Window.Resources>
<!-- Disable TextBox Style -->
<Style x:Key="_DisableTextBoxStyle" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<!--
Be sure to apply all necessary TemplateBindings between
the TextBox and TextBlock template.
-->
<TextBlock Text="{TemplateBinding Text}"
FontFamily="{TemplateBinding FontFamily}"
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox IsEnabled="{Binding IsChecked, ElementName=uiIsEnabled}"
Style="{StaticResource _DisableTextBoxStyle}"
/>
<ToggleButton x:Name="uiIsEnabled" Content="Enable" IsChecked="True" />
</StackPanel>
</Window>
INotifyPropertyChanged does not work for classes that derive from DependencyObject.
Editable property in OrderInfoView must be dependency property in order for binding to work correctly, although technically your code is correct but I feel its bug in WPF that when object is dependency object it ignores INotifyPropertyChanged event because it is searching for notification in property system.
<Controls:EditableLabelControl x:Name="testCtrl"
EditableMode="{Binding Path=Editable,ElementName=userControl}" TextBoxValue="John Smith" Grid.Row="0"/>
Specify ElementName in binding tag and also name your usercontrol with x:FieldName or x:Name
I just came across this searching for something else.
Without reading your post in detail (no time atm sorry) it seems to me you're having a similar issue to the one I posted about here:
http://jonsblogat.blogspot.com/2009/11/wpf-windowdatacontext-and.html
In short, move your binding for your main window to the Grid and use a relative binding to see if that fixes your problem.