Monday, April 11, 2011

How to bind to Popup.IsOpen to Validation.HasError in code

I have a Textbox which is bound to my data object. If the validation fails I would like to show a popup which contains the error message. In XAML this works fine. I'm using the following XAML:

<TextBox Height="23" Margin="54,12,104,0" Name="textBox1" 
VerticalAlignment="Top" Text="{Binding Value, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}"></TextBox>

        <Popup Name="myPopup" PlacementTarget="{Binding ElementName=textBox1}"
                       IsOpen="{Binding ElementName=textBox1, Path=(Validation.HasError), Mode=OneWay}"
                       >
            <TextBlock Name="myPopupText" Background="LightBlue" Foreground="Blue">
                        The value is invalid
            </TextBlock>
        </Popup>

My problem is that I have to create the popup and binding in code and I cannot get it to work. I have tried several different options. I also used dummy converter just to see whether the binding works at all. It seems that the binding works when I create it (it gets the initial value) but after that nothing happens. I can see that the Validation.HasError updates correctly (TextBox's border turns red), but that's it. My dummy converter is not called. Here is the code I'm using:

    Popup popup = new Popup();
    popup.Name = "somepopup";
    // Source is the textbox which is bound to the data object
    popup.PlacementTarget = source;
    popup.Placement = PlacementMode.Bottom;
    TextBlock txtblock = new TextBlock();
    txtblock.Background = Brushes.LightBlue;
    txtblock.Foreground = Brushes.Blue;
    txtblock.Text = "this is the error message";
    popup.Child = txtblock;

    Binding is_open_binding = new Binding("(Validation.HasError)");
    is_open_binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
    is_open_binding.Source = source;
    is_open_binding.Mode = BindingMode.OneWay;
    is_open_binding.NotifyOnValidationError = true;
    is_open_binding.ValidatesOnExceptions = true;
    is_open_binding.Converter = new TempValueConverter();
    popup.SetBinding(Popup.IsOpenProperty, is_open_binding);
From stackoverflow
  • Just did a simple test and it worked fine. Here is my XAML:

    <Window x:Name="_root" x:Class="WpfApplication1.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">
        <StackPanel>
         <TextBox x:Name="_textBox">
          <TextBox.Text>
           <Binding Path="Text" ElementName="_root" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
             <ExceptionValidationRule/>
            </Binding.ValidationRules>
           </Binding>
          </TextBox.Text>
         </TextBox>
         <!--<Popup x:Name="_popup" IsOpen="{Binding (Validation.HasError), ElementName=_textBox, Mode=OneWay}">-->
         <Popup x:Name="_popup">
          <Border BorderThickness="1" BorderBrush="Black" Background="White">
           <TextBlock>Here I am.</TextBlock>
          </Border>
         </Popup>
        </StackPanel>
    </Window>
    

    And here is the code-behind:

    using System;
    using System.Windows;
    using System.Windows.Controls.Primitives;
    using System.Windows.Data;
    
    namespace WpfApplication1
    {
        public partial class Window1 : Window
        {
         public string Text
         {
          get { return "Text"; }
          set { if (value != "Text") throw new InvalidOperationException("Bla"); }
         }
    
         public Window1()
         {
          InitializeComponent();
    
          var binding = new Binding("(Validation.HasError)");
          binding.Source = _textBox;
          binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
          binding.Mode = BindingMode.OneWay;
          binding.NotifyOnValidationError = true;
          binding.ValidatesOnExceptions = true;
          //binding.Converter = new TempValueConverter();
          _popup.SetBinding(Popup.IsOpenProperty, binding);
         }
    
         private sealed class TempValueConverter : IValueConverter
         {
          #region IValueConverter Members
    
          public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
          {
           throw new NotImplementedException();
          }
    
          public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
          {
           throw new NotImplementedException();
          }
    
          #endregion
         }
        }
    }
    

    HTH, Kent

  • I also made a simple solution and copied in your code and it ran just fine. Kent had his Popup declared in XAML, but I used your exact code for creating the Popup and setting the binding, so that difference shouldn't be the cause of the problem for you.

    I am wondering if you could post where your source variable is coming from. You don't show that and I am wondering if it is what you think it is.

    Another thing you might posibly try is keeping a reference to the popup around incase it is being garbage collected. I believe this could be possible as if I recall correctly the binding uses a week event handler for change notification so their wouldn't be any lasting link to the Popup instance. I think this is unlikely however, but might be worth a shot.

    FYI the code I used to test this is as follows.

    XAML file:

    <Window x:Class="PopupOpenBindingTest.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>
        <TextBox Height="23"
                 Margin="54,12,104,0"
                 Name="textBox1"
                 VerticalAlignment="Top"
                 Text="{Binding Text, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" />
    </Grid></Window>
    

    Code-behind.

    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
    
            DataContext = new DataObjectTest();
            this.Loaded += new RoutedEventHandler(Window1_Loaded);
        }
    
        void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            TextBox source = textBox1;
    
            Popup popup = new Popup(); 
            popup.Name = "somepopup";
            popup.PlacementTarget = source; 
            popup.Placement = PlacementMode.Bottom; 
            TextBlock txtblock = new TextBlock(); 
            txtblock.Background = Brushes.LightBlue; 
            txtblock.Foreground = Brushes.Blue; 
            txtblock.Text = "this is the error message"; 
            popup.Child = txtblock;
            Binding is_open_binding = new Binding("(Validation.HasError)");// { Path = new PropertyPath(Validation.HasErrorProperty) }; 
            is_open_binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; 
            is_open_binding.Source = source; 
            is_open_binding.Mode = BindingMode.OneWay; 
            is_open_binding.NotifyOnValidationError = true; 
            is_open_binding.ValidatesOnExceptions = true; 
            //is_open_binding.Converter = new TempValueConverter(); 
            popup.SetBinding(Popup.IsOpenProperty, is_open_binding);
        }
    
        public class DataObjectTest
        {
            private string _text = string.Empty;
    
            public string Text
            {
                get { return _text; }
                set
                {
                    if (value.Length > 5)
                        throw new InvalidOperationException("Blah blah blah");
    
                    _text = value;
                }
            }
        }
    

0 comments:

Post a Comment