programing

Silverlight와 유사한 WPF에서의 검증 오류 스타일

batch 2023. 4. 11. 21:51
반응형

Silverlight와 유사한 WPF에서의 검증 오류 스타일

" " 입니다.Validation.ErrorTemplateWPF에서는 작은 빨간색 테두리만 표시되며,ToolTip.

Silverlight 4 에서는, 검증 에러는, 즉시 사용할 수 있도록 스타일링 되어 있습니다.

다음으로 Silverlight 4와 WPF에서 발생한 검증 오류의 비교입니다.

실버라이트 4
여기에 이미지 설명 입력
WPF
여기에 이미지 설명 입력

WPF 버전은 Silverlight의 멋진 모습과 비교해도 정말 평평하고 지루한 모습이라는 것을 알 수 있습니다.

WPF Framework에 유사한 검증 스타일/템플릿이 존재합니까?또는 상기 Silverlight 버전처럼 스타일 좋게 작성된 검증 템플릿이 있습니까?아니면 처음부터 작성해야 하나요?

Silverlight와 WPF 모두에서 사용할 수 있는 위의 검증 오류를 다음 코드로 재현할 수 있습니다.

메인 윈도/MainPage.xaml

<StackPanel Orientation="Horizontal" Margin="10" VerticalAlignment="Top">
    <TextBox Text="{Binding Path=TextProperty, Mode=TwoWay, ValidatesOnExceptions=True}"/>
    <Button Content="Tab To Me..." Margin="20,0,0,0"/>
</StackPanel>

메인 윈도 / MainPage.xaml.cs

public MainWindow/MainPage()
{
    InitializeComponent();
    this.DataContext = this;
}

private string _textProperty;
public string TextProperty
{
    get { return _textProperty; }
    set
    {
        if (value.Length > 5)
        {
            throw new Exception("Too many characters");
        }
        _textProperty = value;
    }
}

검증 오류 템플릿의 Silverlight 버전을 연구하여 다음과 같은 WPF 버전을 만들었습니다.

여기에 이미지 설명 입력
게시물 하단에 애니메이션 GIF를 추가했지만, 완성 후 마우스 움직임으로 인해 귀찮을 수 있다는 것을 알게 되었습니다.제거해야 하는지 알려주세요.:)

사용하였습니다.MultiBinding a BooleanOrConvertertip-error(팁 오류)가 때 TextBox키보드의 포커스가 있거나 마우스가 오른쪽 상단 모서리에 있습니다.은 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★.DoubleAnimation★★★★★★★★★★★★★★★★의 경우Opacity a. a. a.ThicknessAnimation a BackEase/EaseOut EasingFunction★★★★★★★★★★★★★★★★의 경우Margin

이렇게 사용 가능

<TextBox Validation.ErrorTemplate="{StaticResource errorTemplateSilverlightStyle}" />

errorTemplateSilverlightStyle

<ControlTemplate x:Key="errorTemplateSilverlightStyle">
    <StackPanel Orientation="Horizontal">
        <Border BorderThickness="1" BorderBrush="#FFdc000c" CornerRadius="0.7"
                VerticalAlignment="Top">
            <Grid>
                <Polygon x:Name="toolTipCorner"
                         Grid.ZIndex="2"
                         Margin="-1"
                         Points="6,6 6,0 0,0" 
                         Fill="#FFdc000c" 
                         HorizontalAlignment="Right" 
                         VerticalAlignment="Top"
                         IsHitTestVisible="True"/>
                <Polyline Grid.ZIndex="3"
                          Points="7,7 0,0" Margin="-1" HorizontalAlignment="Right" 
                          StrokeThickness="1.5"
                          StrokeEndLineCap="Round"
                          StrokeStartLineCap="Round"
                          Stroke="White"
                          VerticalAlignment="Top"
                          IsHitTestVisible="True"/>
                <AdornedElementPlaceholder x:Name="adorner"/>
            </Grid>
        </Border>
        <Border x:Name="errorBorder" Background="#FFdc000c" Margin="1,0,0,0"
                Opacity="0" CornerRadius="1.5"
                IsHitTestVisible="False"
                MinHeight="24" MaxWidth="267">
            <Border.Effect>
                <DropShadowEffect ShadowDepth="2.25" 
                                  Color="Black" 
                                  Opacity="0.4"
                                  Direction="315"
                                  BlurRadius="4"/>
            </Border.Effect>
            <TextBlock Text="{Binding ElementName=adorner,
                                      Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                       Foreground="White" Margin="8,3,8,3" TextWrapping="Wrap"/>
        </Border>
    </StackPanel>
    <ControlTemplate.Triggers>
        <DataTrigger Value="True">
            <DataTrigger.Binding>
                <MultiBinding Converter="{StaticResource BooleanOrConverter}">
                    <Binding ElementName="adorner" Path="AdornedElement.IsKeyboardFocused" />
                    <Binding ElementName="toolTipCorner" Path="IsMouseOver"/>
                </MultiBinding>
            </DataTrigger.Binding>
            <DataTrigger.EnterActions>
                <BeginStoryboard x:Name="fadeInStoryboard">
                    <Storyboard>
                        <DoubleAnimation Duration="00:00:00.15"
                                         Storyboard.TargetName="errorBorder"
                                         Storyboard.TargetProperty="Opacity"
                                         To="1"/>
                        <ThicknessAnimation Duration="00:00:00.15"
                                            Storyboard.TargetName="errorBorder"
                                            Storyboard.TargetProperty="Margin"
                                            FillBehavior="HoldEnd"
                                            From="1,0,0,0"
                                            To="5,0,0,0">
                            <ThicknessAnimation.EasingFunction>
                                <BackEase EasingMode="EaseOut" Amplitude="2"/>
                            </ThicknessAnimation.EasingFunction>
                        </ThicknessAnimation>
                    </Storyboard>
                </BeginStoryboard>
            </DataTrigger.EnterActions>
            <DataTrigger.ExitActions>
                <StopStoryboard BeginStoryboardName="fadeInStoryboard"/>
                <BeginStoryboard x:Name="fadeOutStoryBoard">
                    <Storyboard>
                        <DoubleAnimation Duration="00:00:00"
                                         Storyboard.TargetName="errorBorder"
                                         Storyboard.TargetProperty="Opacity"
                                         To="0"/>
                    </Storyboard>
                </BeginStoryboard>
            </DataTrigger.ExitActions>
        </DataTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Boolean Or Converter(부울 오르 컨버터)

public class BooleanOrConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        foreach (object value in values)
        {
            if ((bool)value == true)
            {
                return true;
            }
        }
        return false;
    }
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

여기에 이미지 설명 입력

이 대답은 Fredrik Hedblad의 훌륭한 답변을 확장한 것일 뿐이다.WPF 및 XAML에 익숙하지 않은 Fredrik의 답변은 응용 프로그램에 검증 오류를 표시하는 방법을 정의하는 발판이 되었습니다.아래 XAML은 저에게 효과가 있지만, 현재 진행 중인 작업입니다.저는 아직 충분히 테스트하지 않았고, 모든 태그에 대해 충분히 설명할 수 없다는 것을 쉽게 인정합니다.이러한 주의사항과 함께 이것이 다른 사람들에게 도움이 되기를 바랍니다.

애니메이션 TextBlock은 훌륭한 접근법이지만, 제가 다루고 싶었던 두 가지 단점이 있습니다.

  1. 첫째, Brent의 코멘트가 지적했듯이 텍스트는 소유 창의 경계에 의해 제약되므로 비활성 컨트롤이 창의 가장자리에 있으면 텍스트가 잘립니다.프레드릭이 제안한 해결책은 "창밖에" 전시하는 것이었다.일리가 있는 말이군요.
  2. 둘째, 비활성 컨트롤의 오른쪽에 텍스트 블록을 표시하는 것이 항상 최적인 것은 아닙니다.예를 들어 TextBlock을 사용하여 열 특정 파일을 지정하고 오른쪽에 Browse 버튼이 있다고 가정합니다.사용자가 존재하지 않는 파일에 입력하면 오류 TextBlock이 [Browse]버튼을 커버하여 사용자가 오류를 수정하기 위해 클릭할 수 없게 됩니다.잘못된 컨트롤의 대각선 위 및 오른쪽에 오류 메시지가 표시되는 것이 이해가 됩니다.이것은 두 가지를 달성한다.첫째, 비활성 컨트롤의 오른쪽에 보조 컨트롤이 숨겨지는 것을 방지합니다.또한 toolTipCorner가 오류 메시지를 가리키고 있는 시각적 효과도 있습니다.

이 대화는 제가 개발한 것입니다.

기본 대화 상자

보시는 바와 같이 검증이 필요한 TextBox 컨트롤이 2개 있습니다.둘 다 창의 오른쪽 끝에 비교적 가깝기 때문에 긴 오류 메시지가 잘릴 수 있습니다.번째 TextBox에는 오류 발생 시 숨기지 않는 찾아보기 버튼이 있습니다.

실장에서의 검증 에러는 다음과 같습니다.

여기에 이미지 설명 입력

기능적으로는 Fredrik의 구현과 매우 유사합니다.TextBox에 포커스가 있으면 오류가 표시됩니다.포커스를 잃으면 에러는 사라집니다.사용자가 toolTipCorner 위로 마우스를 가져가면 TextBox에 포커스가 있는지 여부에 관계없이 오류가 나타납니다.toolTipCorner가 50% 더 커지는 등 외관상의 변경도 몇 가지 있습니다(9픽셀 대 6픽셀).

물론 분명한 차이는 구현에서 팝업을 사용하여 오류를 표시한다는 것입니다.그러면 팝업 창에 내용이 표시되므로 대화상자의 테두리에 구속되지 않으므로 첫 번째 단점이 해결됩니다.그러나 팝업을 사용하면 극복해야 할 몇 가지 과제가 있었습니다.

  1. 테스트 및 온라인 토론에서 팝업은 최상위 창으로 간주됩니다.그래서 다른 응용 프로그램에 의해 내 응용 프로그램이 숨겨져 있어도 팝업은 여전히 볼 수 있었다.이것은 바람직하지 않은 행동이었다.
  2. 다른 하나는 팝업 표시 중에 사용자가 대화 상자를 이동하거나 크기를 조정한 경우 팝업은 비활성화된 컨트롤에 대한 위치를 유지하도록 위치를 조정하지 않는다는 것입니다.

다행히 이 두 가지 과제가 모두 해결되었습니다.

여기 암호가 있습니다.코멘트나 개선은 환영입니다!


  • 파일: ErrorTemplateSilverlightStyle.xaml
  • 네임스페이스:MyApp.어플리케이션UI.템플릿
  • 어셈블리:MyApp.어플리케이션UI.dll

<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
  xmlns:behaviors="clr-namespace:MyApp.Application.UI.Behaviors">

  <ControlTemplate x:Key="ErrorTemplateSilverlightStyle">
    <StackPanel Orientation="Horizontal">
      <!-- Defines TextBox outline border and the ToolTipCorner -->
      <Border x:Name="border" BorderThickness="1.25"
                              BorderBrush="#FFDC000C">
        <Grid>
          <Polygon x:Name="toolTipCorner"
                   Grid.ZIndex="2"
                   Margin="-1"
                   Points="9,9 9,0 0,0"
                   Fill="#FFDC000C"
                   HorizontalAlignment="Right"
                   VerticalAlignment="Top"
                   IsHitTestVisible="True"/>
          <Polyline Grid.ZIndex="3"
                    Points="10,10 0,0"
                    Margin="-1"
                    HorizontalAlignment="Right"
                    StrokeThickness="1.5"
                    StrokeEndLineCap="Round"
                    StrokeStartLineCap="Round"
                    Stroke="White"
                    VerticalAlignment="Top"
                    IsHitTestVisible="True"/>
          <AdornedElementPlaceholder x:Name="adorner"/>
        </Grid>
      </Border>
      <!-- Defines the Popup -->
      <Popup x:Name="placard"
             AllowsTransparency="True"
             PopupAnimation="Fade"
             Placement="Top"
             PlacementTarget="{Binding ElementName=toolTipCorner}"
             PlacementRectangle="10,-1,0,0">
        <!-- Used to reposition Popup when dialog moves or resizes -->
        <i:Interaction.Behaviors>
          <behaviors:RepositionPopupBehavior/>
        </i:Interaction.Behaviors>
        <Popup.Style>
          <Style TargetType="{x:Type Popup}">
            <Style.Triggers>
              <!-- Shows Popup when TextBox has focus -->
              <DataTrigger Binding="{Binding ElementName=adorner, Path=AdornedElement.IsFocused}"
                           Value="True">
                <Setter Property="IsOpen" Value="True"/>
              </DataTrigger>
              <!-- Shows Popup when mouse hovers over ToolTipCorner -->
              <DataTrigger Binding="{Binding ElementName=toolTipCorner, Path=IsMouseOver}"
                           Value="True">
                <Setter Property="IsOpen" Value="True"/>
              </DataTrigger>
              <!-- Hides Popup when window is no longer active -->
              <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=IsActive}"
                           Value="False">
                <Setter Property="IsOpen" Value="False"/>
              </DataTrigger>
            </Style.Triggers>
          </Style>
        </Popup.Style>
        <Border x:Name="errorBorder"
                Background="#FFDC000C"
                Margin="0,0,8,8"
                Opacity="1"
                CornerRadius="4"
                IsHitTestVisible="False"
                MinHeight="24"
                MaxWidth="267">
          <Border.Effect>
            <DropShadowEffect ShadowDepth="4"
                              Color="Black"
                              Opacity="0.6"
                              Direction="315"
                              BlurRadius="4"/>
          </Border.Effect>
          <TextBlock Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"
                     Foreground="White"
                     Margin="8,3,8,3"
                     TextWrapping="Wrap"/>
        </Border>
      </Popup>
    </StackPanel>
  </ControlTemplate>

</ResourceDictionary>


  • 파일: RepositionPopupBehavior.cs
  • 네임스페이스:MyApp.어플리케이션UI. 동작
  • 어셈블리:MyApp.어플리케이션UI.dll

(주의: 여기에는 Expression Blend 4 시스템이 필요합니다.윈도인터랙티브 어셈블리)

using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Interactivity;

namespace MyApp.Application.UI.Behaviors
{
    /// <summary>
    /// Defines the reposition behavior of a <see cref="Popup"/> control when the window to which it is attached is moved or resized.
    /// </summary>
    /// <remarks>
    /// This solution was influenced by the answers provided by <see href="https://stackoverflow.com/users/262204/nathanaw">NathanAW</see> and
    /// <see href="https://stackoverflow.com/users/718325/jason">Jason</see> to
    /// <see href="https://stackoverflow.com/questions/1600218/how-can-i-move-a-wpf-popup-when-its-anchor-element-moves">this</see> question.
    /// </remarks>
    public class RepositionPopupBehavior : Behavior<Popup>
    {
        #region Protected Methods

        /// <summary>
        /// Called after the behavior is attached to an <see cref="Behavior.AssociatedObject"/>.
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();
            var window = Window.GetWindow(AssociatedObject.PlacementTarget);
            if (window == null) { return; }
            window.LocationChanged += OnLocationChanged;
            window.SizeChanged     += OnSizeChanged;
            AssociatedObject.Loaded += AssociatedObject_Loaded;
        }

        void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            //AssociatedObject.HorizontalOffset = 7;
            //AssociatedObject.VerticalOffset = -AssociatedObject.Height;
        }

        /// <summary>
        /// Called when the behavior is being detached from its <see cref="Behavior.AssociatedObject"/>, but before it has actually occurred.
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();
            var window = Window.GetWindow(AssociatedObject.PlacementTarget);
            if (window == null) { return; }
            window.LocationChanged -= OnLocationChanged;
            window.SizeChanged     -= OnSizeChanged;
            AssociatedObject.Loaded -= AssociatedObject_Loaded;
        }

        #endregion Protected Methods

        #region Private Methods

        /// <summary>
        /// Handles the <see cref="Window.LocationChanged"/> routed event which occurs when the window's location changes.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// An object that contains the event data.
        /// </param>
        private void OnLocationChanged(object sender, EventArgs e)
        {
            var offset = AssociatedObject.HorizontalOffset;
            AssociatedObject.HorizontalOffset = offset + 1;
            AssociatedObject.HorizontalOffset = offset;
        }

        /// <summary>
        /// Handles the <see cref="Window.SizeChanged"/> routed event which occurs when either then <see cref="Window.ActualHeight"/> or the
        /// <see cref="Window.ActualWidth"/> properties change value.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// An object that contains the event data.
        /// </param>
        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            var offset = AssociatedObject.HorizontalOffset;
            AssociatedObject.HorizontalOffset = offset + 1;
            AssociatedObject.HorizontalOffset = offset;
        }

        #endregion Private Methods
    }
}


  • 파일: ResourceLibrary.xaml
  • 네임스페이스:MyApp.어플리케이션UI
  • 어셈블리:MyApp.어플리케이션UI.dll

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <ResourceDictionary.MergedDictionaries>

        <!-- Styles -->
        ...

        <!-- Templates -->
        <ResourceDictionary Source="Templates/ErrorTemplateSilverlightStyle.xaml"/>

    </ResourceDictionary.MergedDictionaries>

    <!-- Converters -->
    ...

</ResourceDictionary>


  • 파일: App.xaml
  • 네임스페이스:MyApp.어플리케이션
  • 어셈블리:MyApp.exe

<Application x:Class="MyApp.Application.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="Views\MainWindowView.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/MyApp.Application.UI;component/ResourceLibrary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>


  • 파일: New Project View.xaml
  • 네임스페이스:MyApp.어플리케이션표시
  • 어셈블리:MyApp.exe

<Window x:Class="MyApp.Application.Views.NewProjectView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:views="clr-namespace:MyApp.Application.Views"
        xmlns:viewModels="clr-namespace:MyApp.Application.ViewModels"
        Title="New Project" Width="740" Height="480"
        WindowStartupLocation="CenterOwner">

  <!-- DATA CONTEXT -->
  <Window.DataContext>
    <viewModels:NewProjectViewModel/>
  </Window.DataContext>

  <!-- WINDOW GRID -->
  ...

  <Label x:Name="ProjectNameLabel"
         Grid.Column="0"
         Content="_Name:"
         Target="{Binding ElementName=ProjectNameTextBox}"/>
  <TextBox x:Name="ProjectNameTextBox"
           Grid.Column="2"
           Text="{Binding ProjectName,
                          Mode=TwoWay,
                          UpdateSourceTrigger=PropertyChanged,
                          ValidatesOnDataErrors=True}"
           Validation.ErrorTemplate="{StaticResource ErrorTemplateSilverlightStyle}"/>

  ...
</Window>

에러 메시지와 함께 텍스트 상자 바로 아래에 에러 주소를 표시하기 위해 프로젝트 중 하나에 커스텀 에러 주소를 작성했습니다.속성 "Validation"을 설정하기만 하면 됩니다.ErrorTemplate"를 텍스트 상자 기본 스타일로 지정하면 응용 프로그램의 모든 텍스트 상자에 적용되도록 앱 리소스에 유지할 수 있습니다.

주의: 여기에 브러시를 몇 개 사용했습니다.당신의 아도르너 메쉬용 브러시로 교체해 주세요.이것이 도움이 될 수 있습니다.

<Setter Property="Validation.ErrorTemplate">
              <Setter.Value>
                <ControlTemplate>
                  <StackPanel>
                    <!--TextBox Error template-->
                    <Canvas Panel.ZIndex="1099">
                      <DockPanel>
                        <Border BorderBrush="{DynamicResource HighlightRedBackgroundBrush}" BorderThickness="2" Padding="1" CornerRadius="3">
                          <AdornedElementPlaceholder x:Name="ErrorAdorner" />
                        </Border>
                      </DockPanel>
                      <Popup IsOpen="True" AllowsTransparency="True" Placement="Bottom" PlacementTarget="{Binding ElementName=ErrorAdorner}" StaysOpen="False">
                        <Border Canvas.Bottom="4"
                Canvas.Left="{Binding Path=AdornedElement.ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}"
                BorderBrush="{DynamicResource HighlightRedBackgroundBrush}"
                BorderThickness="1"
                Padding="4"
                CornerRadius="5"
                Background="{DynamicResource ErrorBackgroundBrush}">
                          <StackPanel Orientation="Horizontal">
                            <ContentPresenter Width="24" Height="24" Content="{DynamicResource ExclamationIcon}" />
                            <TextBlock TextWrapping="Wrap"
                   Margin="4"
                   MaxWidth="250"
                   Text="{Binding Path=AdornedElement.(Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}" />
                          </StackPanel>
                        </Border>
                      </Popup>
                    </Canvas>
                  </StackPanel>
                </ControlTemplate>
              </Setter.Value>
            </Setter>

현재 진행 중인 wpf 프로젝트에 적용하려고 할 때 문제가 발생했습니다.프로젝트를 실행할 때 다음과 같은 문제가 발생할 경우:

「시스템 타입은 예외입니다.창문들.Markup.XamlParseException'은 PresentationFramework.dll에서 발생했지만 사용자 코드로 처리되지 않았습니다."

리소스(app.xaml 내)에 booleanOrConverter 클래스의 인스턴스를 만들어야 합니다.

<validators:BooleanOrConverter x:Key="myConverter" />

또한 파일 맨 위에 네임스페이스를 추가하는 것도 잊지 마십시오(애플리케이션 태그).

xmlns: Validators="clr-namespace:ParcelRatesViewModel.Validators;assembly=RatesViewModel"

언급URL : https://stackoverflow.com/questions/7434245/validation-error-style-in-wpf-similar-to-silverlight

반응형