views:

1767

answers:

4

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!

A: 

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

Akash Kava
That didn't work either. Editable is also property of the main window and not the user control. And the main window has the data context set to self: DataContext="{Binding RelativeSource={RelativeSource Self}}
Greg R
Then set elementname to main window, give it some name and try that.
Akash Kava
A: 

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));
Greg R
you are missing ElementName in your Binding tag. I changed my answer.
Akash Kava
+1  A: 

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>
Tony Borres
That's really cool, thank you. My only question would be, will this solution allow me to do something like this: change textbox border to red once the value inside of it. When button to toggle is clicked, it will rest the border back to original value next time textbox is shown?Also, any idea why my previous solution was not working?Thanks a lot!!!
Greg R
I'm not exactly sure what you are asking, but there are a lot of things you can do with Trigger and DataTrigger. For instance, you could change the border brush using a setter in a Trigger that checks to see if the Text is {x:Null} or Text.Length is 0.
Tony Borres
Cool, thanks a lot, I will check it out!
Greg R
Thanks, this worked really well for me!
Greg R
Thank you!! I was also trying to do something way too complicated when this was exactly what I needed.
Abby Fichtner
A: 

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.

Jon