2010-07-22 4 views
4

실제 시나리오는 다음과 같습니다. ListView 및 사용자 지정 UserControl이 마스터 세부 정보에 있습니다. 메뉴 항목을 통해 처음에는 유효하지 않은 여러 항목을 추가 할 수 있습니다.ListView의 바운드 항목에 대한 ValidationRules

목록에있는 항목이 유효하지 않은 경우 제출하지 못하게하고 싶습니다. 단기, 나는 유효하지 않은 항목에 대한 시각적 단서를 주려고 노력하고 있습니다. 내 생각은 ListViewListViewItem 트리거를 ListViewItemValidation.HasError 속성이 붙어있는 전체 행의 배경이 빨간색이되도록 트리거하는 스타일을 도입하는 것입니다.

이 작업을 수행하려면 물론 스타일을 추가하고 GridViewColumnDisplayMemberBinding에서 사용하는 간단한 유효성 검사 규칙을 도입했습니다. 디버거에서 규칙이 호출되고 규칙이 예상대로 작동하지만 스타일이 변경되지 않는다고 확인했습니다.

아래의 모든 관련 부분을 복제물에 포함 시켰습니다. 나는 여기에 어떤 도움을 주셔서 감사합니다. 단추는 항상 "유효한"메시지 상자를 생성합니다. 디버거가 실패한 규칙을 보여주고 있음에도 불구하고 텍스트로 표시됩니다.

.Net 3.5 SP1도 사용하고 있습니다.

Person.cs :

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace ListViewItemValidation 
{ 
    public class Person 
    { 
     public string Name { get; set; } 
     public int Age { get; set; } 

     static Person[] _Data; 
     public static Person[] Data 
     { 
      get 
      { 
       if (_Data == null) 
       { 
        _Data =new[]{ 
         new Person() { Name="John", Age=30}, 
         new Person() { Name="Mary", Age=40}, 
         new Person() { Name="", Age=20}, 
         new Person() { Name="Tim", Age=-1}, 
        }; 
       } 
       return _Data; 
      } 
     } 
    } 
} 

RequiredStringValidator.cs :

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Windows; 
using System.Windows.Data; 
using System.Windows.Controls; 

namespace ListViewItemValidation 
{ 
    public class RequiredStringValidator : ValidationRule 
    { 
     public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) 
     { 
      if (string.IsNullOrEmpty(value as string)) 
       return new ValidationResult(false, "String cannot be empty."); 

      return ValidationResult.ValidResult; 
     } 
    } 
} 

Window1.xaml :

<Window 
    x:Class="ListViewItemValidation.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:l="clr-namespace:ListViewItemValidation" 
    Title="Window1" Height="300" Width="300"> 
    <DockPanel> 
     <Button Content="Validate" 
       DockPanel.Dock="Bottom" 
       Click="ValidateClicked" 
       /> 
     <ListView 
      HorizontalAlignment="Stretch"   
      VerticalAlignment="Stretch" 
      ItemsSource="{x:Static l:Person.Data}" 
      > 
      <ListView.Resources> 
       <Style TargetType="ListViewItem"> 
        <Style.Triggers> 
         <Trigger Property="Validation.HasError" Value="True"> 
          <Setter Property="Background" Value="Red" /> 
         </Trigger> 
        </Style.Triggers> 
       </Style> 
      </ListView.Resources> 
      <ListView.View> 
       <GridView> 
        <GridView.Columns> 
         <GridViewColumn Header="Name"> 
          <GridViewColumn.DisplayMemberBinding> 
           <Binding Path="Name"> 
            <Binding.ValidationRules> 
             <l:RequiredStringValidator 
              ValidatesOnTargetUpdated="True" 
              ValidationStep="RawProposedValue" 
              /> 
            </Binding.ValidationRules> 
           </Binding> 
          </GridViewColumn.DisplayMemberBinding> 
         </GridViewColumn> 
         <GridViewColumn 
          Header="Age" 
          DisplayMemberBinding="{Binding Path=Age}" 
          /> 
        </GridView.Columns> 
       </GridView> 
      </ListView.View> 
     </ListView> 
    </DockPanel> 
</Window> 

Window1.xaml.cs :

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Data; 
using System.Windows.Documents; 
using System.Windows.Input; 
using System.Windows.Media; 
using System.Windows.Media.Imaging; 
using System.Windows.Navigation; 
using System.Windows.Shapes; 

namespace ListViewItemValidation 
{ 
    /// <summary> 
    /// Interaction logic for Window1.xaml 
    /// </summary> 
    public partial class Window1 : Window 
    { 
     public Window1() 
     { 
      InitializeComponent(); 
     } 

     private void ValidateClicked(object sender, RoutedEventArgs e) 
     { 
      if (Validation.GetHasError(this)) 
       MessageBox.Show("invalid!"); 
      else 
       MessageBox.Show("valid!"); 
     } 
    } 
} 
01,235,

업데이트 : 최종 솔루션 내가 어떤 아이들이 실패한 유효성 검사 규칙에 바인딩 속성이 포함 된 경우 ListViewItem이 확인을 위해 연결된 속성을 제공하기 위해 다음과 같은 클래스를 추가 :

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Data; 
using System.Windows.Media; 

namespace ListViewItemValidation 
{ 
    public class ListViewItemExtensions 
    { 
     #region ChildrenHaveError Property 

     public bool ChildrenHaveError 
     { 
      get { return (bool)this.ListViewItem.GetValue(ChildrenHaveErrorProperty); } 
      set { this.ListViewItem.SetValue(ChildrenHaveErrorProperty, value); } 
     } 

     public static bool GetChildrenHaveError(ListViewItem obj) 
     { 
      return EnsureInstance(obj).ChildrenHaveError; 
     } 

     public static void SetChildrenHaveError(ListViewItem obj, bool value) 
     { 
      EnsureInstance(obj).ChildrenHaveError = value; 
     } 

     public static readonly DependencyProperty ChildrenHaveErrorProperty = 
      DependencyProperty.RegisterAttached(
       "ChildrenHaveError", 
       typeof(bool), 
       typeof(ListViewItemExtensions), 
       new PropertyMetadata(
        new PropertyChangedCallback((o, a) => { EnsureInstance((ListViewItem)o); }) 
       ) 
     ); 
     #endregion 

     #region ValidatesChildren Property 
     public bool ValidatesChildren 
     { 
      get { return (bool)this.ListViewItem.GetValue(ValidatesChildrenProperty); } 
      set { this.ListViewItem.SetValue(ValidatesChildrenProperty, value); } 
     } 

     public static bool GetValidatesChildren(ListViewItem obj) 
     { 
      return EnsureInstance(obj).ValidatesChildren; 
     } 

     public static void SetValidatesChildren(ListViewItem obj, bool value) 
     { 
      EnsureInstance(obj).ValidatesChildren = value; 
     } 

     public static readonly DependencyProperty ValidatesChildrenProperty = 
      DependencyProperty.RegisterAttached(
       "ValidatesChildren", 
       typeof(bool), 
       typeof(ListViewItemExtensions), 
       new PropertyMetadata(
        new PropertyChangedCallback((o, a) => { EnsureInstance((ListViewItem)o); }) 
       ) 
      ); 
     #endregion 

     #region Instance Property 
     public static ListViewItemExtensions GetInstance(ListViewItem obj) 
     { 
      return (ListViewItemExtensions)obj.GetValue(InstanceProperty); 
     } 

     public static void SetInstance(ListViewItem obj, ListViewItemExtensions value) 
     { 
      obj.SetValue(InstanceProperty, value); 
     } 

     public static readonly DependencyProperty InstanceProperty = 
      DependencyProperty.RegisterAttached("Instance", typeof(ListViewItemExtensions), typeof(ListViewItemExtensions)); 
     #endregion 

     #region ListViewItem Property 
     public ListViewItem ListViewItem { get; private set; } 
     #endregion 

     static ListViewItemExtensions EnsureInstance(ListViewItem item) 
     { 
      var i = GetInstance(item); 
      if (i == null) 
      { 
       i = new ListViewItemExtensions(item); 
       SetInstance(item, i); 
      } 
      return i; 
     } 

     ListViewItemExtensions(ListViewItem item) 
     { 
      if (item == null) 
       throw new ArgumentNullException("item"); 

      this.ListViewItem = item; 
      item.Loaded += (o, a) => 
      { 
       this.FindBindingExpressions(item); 
       this.ChildrenHaveError = ComputeHasError(item); 
      }; 
     } 

     static bool ComputeHasError(DependencyObject obj) 
     { 
      var e = obj.GetLocalValueEnumerator(); 

      while (e.MoveNext()) 
      { 
       var entry = e.Current; 

       if (!BindingOperations.IsDataBound(obj, entry.Property)) 
        continue; 

       var binding = BindingOperations.GetBinding(obj, entry.Property); 
       foreach (var rule in binding.ValidationRules) 
       { 
        ValidationResult result = rule.Validate(obj.GetValue(entry.Property), null); 
        if (!result.IsValid) 
        { 
         BindingExpression expression = BindingOperations.GetBindingExpression(obj, entry.Property); 
         Validation.MarkInvalid(expression, new ValidationError(rule, expression, result.ErrorContent, null)); 
         return true; 
        } 
       } 
      } 

      for (int i = 0, count = VisualTreeHelper.GetChildrenCount(obj); i < count; ++i) 
       if (ComputeHasError(VisualTreeHelper.GetChild(obj, i))) 
        return true; 

      return false; 
     } 

     void OnDataTransfer(object sender, DataTransferEventArgs args) 
     { 
      this.ChildrenHaveError = ComputeHasError(this.ListViewItem); 
     } 

     void FindBindingExpressions(DependencyObject obj) 
     { 
      var e = obj.GetLocalValueEnumerator(); 

      while (e.MoveNext()) 
      { 
       var entry = e.Current; 
       if (!BindingOperations.IsDataBound(obj, entry.Property)) 
        continue; 

       Binding binding = BindingOperations.GetBinding(obj, entry.Property); 
       if (binding.ValidationRules.Count > 0) 
       { 
        Binding.AddSourceUpdatedHandler(obj, new EventHandler<DataTransferEventArgs>(this.OnDataTransfer)); 
        Binding.AddTargetUpdatedHandler(obj, new EventHandler<DataTransferEventArgs>(this.OnDataTransfer)); 
       } 
      } 

      for (int i = 0, count = VisualTreeHelper.GetChildrenCount(obj); i < count; ++i) 
      { 
       var child = VisualTreeHelper.GetChild(obj, i); 
       this.FindBindingExpressions(child); 
      } 
     } 

    } 
} 

을 그 다음을, 나는 ListViewItem 스타일을 수정 다음과 같습니다 :

 <Style TargetType="ListViewItem"> 
      <Style.Setters> 
       <Setter Property="l:ListViewItemExtensions.ValidatesChildren" Value="True" /> 
      </Style.Setters> 
      <Style.Triggers> 
       <Trigger Property="l:ListViewItemExtensions.ChildrenHaveError" Value="True"> 
        <Setter Property="Background" Value="Red" /> 
       </Trigger> 
      </Style.Triggers> 
     </Style> 

@Quartermeister에게 감사드립니다.

답변

3

Validation.HasError은 바인딩이 적용되는 개별 셀의 TextBlock에만 설정됩니다. 이것은 ListViewItem의 자식 중 하나이지만 ListViewItem 자체는 아닙니다. Window에 설정되어 있지 않으므로 메시지 상자에 항상 "valid!"가 표시됩니다.

하나의 셀이 유효성 검사에 실패 할 때 전체 행을 강조 표시하는 데 사용할 수있는 방법 중 하나는 셀이 해당 행이되도록 ValidationAdornerSite을 설정하는 것입니다. 이렇게하면 ListViewItem에 ErrorTemplate이 적용됩니다. 기본적으로 빨간색 테두리가 표시됩니다. 다음과 같은 스타일을 추가해보십시오.

<Style TargetType="TextBlock"> 
    <Setter 
     Property="Validation.ValidationAdornerSite" 
     Value="{Binding RelativeSource={RelativeSource AncestorType=ListViewItem}}"/> 
</Style> 
+0

나는 잠시 동안 놀아 왔으며, 제대로 작동하지 못했습니다.TextBlock이 전혀 유효성 검사를하지 않는 것처럼 보입니다 (유효성 검사 규칙이 호출되는 것을 볼 수는 있지만). 저는 VisualTreeHelper를 사용하여 ListViewItem의 자식을 검사하고 발견 된 각'TextBlock'에서 수동으로'Validation.GetHasError'를 호출했습니다 (물론'GridViewRowPresenter'를 탐색 함). 그리고 항상 false를 반환합니다. 유효성 검사 규칙이 실패했습니다. 어떤 아이디어? –

+0

@Nathan : 질문에 게시 한 코드를 정확하게 실행하면 Validation.GetHasError는 빈 이름 문자열에 대한 TextBlock에서 true를 반환합니다. 3.5 SP1도 실행 중이므로 어떤 차이가 있는지 알 수 없습니다. – Quartermeister

+0

@Quarter 마이 스터, 호기심 ... 나는 감소 된 코드로 조금 더 깊게 파헤쳐 야 할 것이다. 실제 앱에서 사용해야하는 회사 전체 스타일이 트리거에 영향을 줄 수 있습니다 (이는 최근에 다양한 방법을 테스트 해왔음). 내가 찾은 것을 알려주지. –

관련 문제