[WPF] PasswordBox의 Tag가 placeholder로 표시되지 않습니다.

안녕하세요.

로그인화면

로그인 페이지를 만들고 있습니다.
이메일과 비밀번호 칸이 있습니다.
이메일 칸은 TextBox로 구현했구요, 비밀번호칸은 PasswordBox로 구현하고 있습니다.
TextBox에 사용한 Style 소스로 같이 구현했는데 PaswordBox칸은 Tag가 나타나지 않습니다.

<!-- Email -->
<TextBox Style="{StaticResource TextBoxIDPwd}" Tag="Email" />
<!-- Password -->
  <PasswordBox Style="{StaticResource Test1}" Tag="Password"/>
<!-- TexBox Style 설정 -->
    <Style x:Key="TextBoxIDPwd" TargetType="{x:Type TextBox}" BasedOn="{StaticResource BaseStyle}" >
      ...
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBoxBase}">
                    <Grid>
                        <Border x:Name="border" 
                                Background="{TemplateBinding Background}" 
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                SnapsToDevicePixels="True">
                            <ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                        </Border>

                        <TextBlock IsHitTestVisible="False"
                                   Text="{TemplateBinding Tag}"
                                   x:Name="placeholder"
                                   FontFamily="{StaticResource LatoThin}"
                                   Padding="{TemplateBinding Padding}"
                                   VerticalAlignment="Center"
                                   HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
                                   Foreground="{StaticResource ForegroundVeryDarkBrush}" >

                            <TextBlock.Style>
                                <Style TargetType="{x:Type TextBlock}">
                                    <Setter Property="Visibility" Value="Collapsed" />
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource TemplatedParent}}" Value="">
<!-- PaswordBox =>  <DataTrigger Binding="{Binding (local:PasswordBoxProperties.HasText), RelativeSource={RelativeSource TemplatedParent}}" Value="False"> -->
                                            <Setter Property="Visibility" Value="Visible" />
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </TextBlock.Style>
                        </TextBlock>
                    </Grid>
            </Setter.Value>
        </Setter>
    </Style>
<!-- PasswordBox Style 설정 -->
<Style x:Key="Test1" TargetType="{x:Type PasswordBox}" BasedOn="{StaticResource BaseStyle}" >
      ...
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type PasswordBox}">
                    <Grid>
                        <Border x:Name="border" 
                                Background="{TemplateBinding Background}" 
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                SnapsToDevicePixels="True">
                            <ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                        </Border>

                        <TextBlock IsHitTestVisible="False"
                                   Text="{TemplateBinding Tag}"
                                   x:Name="placeholder"
                                   FontFamily="{StaticResource LatoThin}"
                                   Padding="{TemplateBinding Padding}"
                                   VerticalAlignment="Center"
                                   HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
                                   Foreground="{StaticResource ForegroundVeryDarkBrush}" >

                            <TextBlock.Style>
                                <Style TargetType="{x:Type TextBlock}">
                                    <Setter Property="Visibility" Value="Collapsed" />
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding (local:PasswordBoxProperties.HasText), RelativeSource={RelativeSource TemplatedParent}}" Value="False">
                                            <Setter Property="Visibility" Value="Visible" />
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </TextBlock.Style>
                        </TextBlock>
                    </Grid>

                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Opacity" TargetName="border" Value="0.56"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="BorderBrush" TargetName="border" Value="#FF7EB4EA"/>
                        </Trigger>
                        <Trigger Property="IsKeyboardFocused" Value="true">
                            <Setter Property="BorderBrush" TargetName="border" Value="#FF569DE5"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

PasswordBox의 경우 위에 소스를 복붙하고 TargetType을 PasswordBox로 설정해주었습니다.
PasswordBox는 실링되어 있어서 별도의 cs를 만들어서 이벤트 처리등등을 하고 있습니다.

저장소에 사용한 소스 ( PasswordBoxProperties.cs, LoginPage.xaml, Texts.xaml )를 업로드 합니다.
GitHub : https://github.com/SSstupid/WPF_Start_Records/tree/main/PasswordBox%20테스트
감사합니다.


22.04.04 수정

PasswordBox 의 Tag 를 Binding 해서 placeholder 로 보여주는 style 을 만들었다.
근데 style 안에서 AttachedProperty 를 사용했는데 Tag 가 placeholder 로 표시되지 않는다.

질문이 잘 정리됬네요.
위에 긴 글이 이 두줄로 축약되고 이해가 잘됩니다.

1개의 좋아요

답변은 아니지만, 뭔가 엔젤식스 WPF 유튜브에서 본 거 같은 그림이네요.

1개의 좋아요

네 맞습니다. 유튜브에서 WPF 강의보고 있습니다.
C# WPF UI Tutorials: 06 - Attached Properties에 관한 영상입니다.
똑같이 따라하는건데 오류가 나네요. :sweat_smile:

1개의 좋아요

저 부분은 제가 해본 적이 없어서 답변이 어려울 거 같습니다…ㅠㅠ WPF 고수분께서 답변해주시길…

더불어 엔젤식스 WPF 유튜브 보셨던 분들이…오타도 많고 소스가 영상하고 다르다고들 많이 하시더라구요… 그건 참고하시면 될 것 같습니다.

1개의 좋아요

네 맞습니다. 영상에 사용한 샘플 소스가 있습니다만… 영상 최종 결과물 소스입니다.
중간과정에서 나온 샘플이 없어서 참고하기가 힘드네요…
제가 궁금한 것은 Tag를 구현하는 것이 아닌 현재 PasswordBox에 Tag가 왜 구현안되는지에 대한 것이라
답을 구하기가 좀 힘든것 같습니다.

2개의 좋아요

첨부하신 저장소를 받아봤는데
PasswordBox 나 LoginPage 를 사용한 곳 자체가 없는데요?ㅁ?
어디서 문제가 생기는 건지 알 수가 없네요. =ㅅ=;;
(저장소를 왜 올려놓으신 건지도 잘 모르겠군요;;; )

질문하신 부분에서도 이해가 잘 안가는데

음… 저는 이게 무슨 말인지 잘 모르겠습니다.
PasswordBox 에 Tag 프로퍼티가 없다는 얘긴지요?
(PasswordBox 가 Control 을 상속받는데 Tag 가 없을 수 없죠.)

그리고 제시하신 xaml 코드는 당연히 TargetType 이 TextBox 니까 PasswordBox 에 style 이 적용 안 되는 게 맞겠죠?

뭐가 문제인거죠?ㅅ?

3개의 좋아요

@Greg.Lee
죄송합니다.
설명이 부족했습니다.

LoginPage.xaml에서 PasswordBox를 생성후 Tag 설정을 시도하고 있습니다.
PasswordBoxProperties.cs, LoginPage.xaml, Texts.xaml 이렇게 3개로 구동이됩니다.
실링

PasswordBoxProperties.cs는 PasswordBox가 실링된 클래스라 우회용으로 생성습니다.(이벤트 설정 등등용으로)
LoginPage.xaml에는 PasswordBox추가하고
Texts.xaml에서 PasswordBox에 대한 Style을 설정합니다.
메인
MainWindow.xaml에서 창 중앙에 LoginPage를 구현합니다.

저장소는 위에 3개를 업로드 했습니다.( PasswordBoxProperties.cs, LoginPage.xaml, Texts.xaml )
단편적인 내용과 부족한 자료첨부로 혼란이 생겼네요.
저장소에 전체 프로젝트를 업로드 했습니다.

위에 소스는 Textbox를 구현한 소스구요. Textbox에서는 Tag가 잘 나옵니다.
똑같은 소스를 복사 붙여넣기 후 TargetType을 PasswordBox으로 변경후

<TextBlock.Style>
    <Style TargetType="{x:Type TextBlock}">
        <Setter Property="Visibility" Value="Collapsed" />
        <Style.Triggers>
            <DataTrigger Binding="{Binding (local:PasswordBoxProperties.HasText), RelativeSource={RelativeSource TemplatedParent}}" Value="False">
                <Setter Property="Visibility" Value="Visible" />
            </DataTrigger>
        </Style.Triggers>

DataTrigger Binding 부분을 “{Binding (local:PasswordBoxProperties.HasText)” 로 변경해서 사용 했습니다.
이렇게 했을시 PasswordBox에는 Tag가 나타나질 않습니다.


PasswordBox에 사용한 소스입니다.

// PasswordBoxProperties.cs
// Set =>  public bool MonitorPassword { get; set; } = false, Event(OnMonitorPasswordChanged)
public class PasswordBoxProperties
    {
        public static readonly DependencyProperty MonitorPasswordProperty =
           DependencyProperty.RegisterAttached("MonitorPassword",
               typeof(bool),
               typeof(PasswordBoxProperties),
               new PropertyMetadata(false, OnMonitorPasswordChanged));

        private static void OnMonitorPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var passwordBox = (d as PasswordBox);

            if (passwordBox == null) 
                return;

            passwordBox.PasswordChanged -= PasswordBox_PasswordChanged;

            if ((bool)e.NewValue)
            {
                SetHasText(passwordBox);

                passwordBox.PasswordChanged += PasswordBox_PasswordChanged;
            }
        }

        private static void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
        {
            SetHasText((PasswordBox)sender);
        }

        public static void SetMonitorPassword(PasswordBox element, bool value)
        {
            element.SetValue(MonitorPasswordProperty, value);
        }

        public static bool GetMonitorPassword(PasswordBox element)
        {
            return (bool)element.GetValue(MonitorPasswordProperty);
        }


// Set => public bool HasText { get; set; } = false
        public static readonly DependencyProperty HasTextProperty = 
            DependencyProperty.RegisterAttached("HasText",
                typeof(bool),
                typeof(PasswordBoxProperties),
                new PropertyMetadata(false));

        private static void SetHasText(PasswordBox element)
        {
            element.SetValue(HasTextProperty, element.SecurePassword.Length > 0);
        }

        public static bool GetHasText(PasswordBox element)
        {
            return (bool)element.GetValue(HasTextProperty);
        }
    }
 <!-- LoginPage.xaml-->
 <PasswordBox Style="{StaticResource Test1}" Tag="Password" Margin="0 0 0 30"/>
<!-- Texts.xaml -->
<Style x:Key="Test1" TargetType="{x:Type PasswordBox}" BasedOn="{StaticResource BaseStyle}" >
      ...
  <Setter Property="local:PasswordBoxProperties.MonitorPassword" Value="True" />

        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type PasswordBox}">
                    <Grid>
                        <Border x:Name="border" 
                                Background="{TemplateBinding Background}" 
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                SnapsToDevicePixels="True">
                            <ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                        </Border>

                        <TextBlock IsHitTestVisible="False"
                                   Text="{TemplateBinding Tag}"
                                   x:Name="placeholder"
                                   FontFamily="{StaticResource LatoThin}"
                                   Padding="{TemplateBinding Padding}"
                                   VerticalAlignment="Center"
                                   HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
                                   Foreground="{StaticResource ForegroundVeryDarkBrush}" >

                            <TextBlock.Style>
                                <Style TargetType="{x:Type TextBlock}">
                                    <Setter Property="Visibility" Value="Collapsed" />
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding (local:PasswordBoxProperties.HasText), RelativeSource={RelativeSource TemplatedParent}}" Value="False">
                                            <Setter Property="Visibility" Value="Visible" />
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </TextBlock.Style>
                        </TextBlock>
                    </Grid>

                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Opacity" TargetName="border" Value="0.56"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="BorderBrush" TargetName="border" Value="#FF7EB4EA"/>
                        </Trigger>
                        <Trigger Property="IsKeyboardFocused" Value="true">
                            <Setter Property="BorderBrush" TargetName="border" Value="#FF569DE5"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
1개의 좋아요

소스 코드를 한 참 보고 나서야 tag 가 안 나타난다는 게 무슨 말인지 알았네요…-ㅅ-;;;

일단

요 부분에서 괄호친 저 영역이 문제 입니다. 저렇게 표시해 놓으면 인식이 안 될 겁니다.

BindingExpression path error: '(local:PasswordBoxProperties.HasText)' property not found on 'object' ''PasswordBox' (Name='')'. BindingExpression:Path=(local:PasswordBoxProperties.HasText); DataItem='PasswordBox' (Name=''); target element is 'TextBlock' (Name='placeholder'); target property is 'NoTarget' (type 'Object')

아마 요런 에러 메시지가 나왔을 텐데 Binding 구문을

Binding="{Binding Path=(local:PasswordBoxProperties.HasText), RelativeSource={RelativeSource TemplatedParent}}"

이걸로 바꿔야 정상적으로 Binding 이 될 거예요.

요거는 AttachedProperty (이하 AP) 스펙에 없는 내용이라 사람들이 자주 질문하는 것이기도 하구요.
DataTrigger 에 AP 를 Binding 하려면 Path=(AP) 요 모양으루 해줘야 합니다.

그 다음으로는 로직 자체가 빠진게 있어요.

PasswordBoxProperties.HasText 요 AP 는 현재 Binding 으로 연결된 구간 없이
PasswordBoxProperties.MonitorPassword 요 내부 setter 에서 강제로 값을 바꿔주고 있지요.

그런데 Test1 스타일에는 PasswordBoxProperties.MonitorPassword 를 사용하는 구간이 없어요.
그러니 당연히 PasswordBoxProperties.HasText 의 값의 변동이 없을 거고
그래서 DataTrigger 가 반응이 없는 겁니다.

PasswordBoxProperties.HasText 를 사용하든가 아니면
PasswordBoxProperties.MonitorPassword 를 사용하든가 둘 중하나를 사용해야합니다.

제가 테스트 해보니 Test1 에

<Setter Property="local:PasswordBoxProperties.MonitorPassword" Value="True" />

요거만 추가해도 잘 동작합니다.

3개의 좋아요

근데 이렇게 질문하시면 앞으로는 답변 받는게 어려울 거 같아 한 말씀 더 남기면요…

정확히 어떤 부분을 알고 싶은 건지,
그 부분만, 그리고 그 부분에 필요한 것들만 간단히 정리해서 이해하기 쉽게 질문을 해야
답변도 제대로 받을 수 있어요.

질문에는 PasswordBox의 Tag 를 Binding 해서 placeholder 로 보여주는 로직에 대한 설명이 없어요.
placeholder 구현이 어떻게 되어 있고, 어떤 게 문제인지 설명 없이 그냥 tag 가 안나온다고 하면
대부분은 그냥 이게 무슨 말인지 모르니 그냥 무시해버립니다.

이런식의 질문은 답변하는 사람이 일일이 읽어서 이해하고 답변을 해야하고 이건 답변하는 사람의 시간을 소모하게 만드는 거예요. =ㅅ=

첫 질문에서 그냥

PasswordBox 의 Tag 를 Binding 해서 placeholder 로 보여주는 style 을 만들었다.
근데 style 안에서 AttachedProperty 를 사용했는데 Tag 가 placeholder 로 표시되지 않는다.

그리고 저 스타일을 포함한 간단한 예제,
즉, 문제가 생기는 예제만 올렸어도 올렸어도 금방 답변을 받을 수 있었을 거예요.

이 정도만 했어도 문제 쉽게 접근할 수 있었을 겁니다.

질문도 잘 해야 답변도 잘 받을 수 있는 건 당연한 거고
답변하기 좋게 질문을 해야 답변하는 사람의 시간도 아끼고 수고도 덜 수 있어요.

이건 일종의 예의 같은 거라 누가 안 가르쳐줘도 입장 바꿔놓고 생각해보면 당연한 거예요.

뭔가 가르치듯 표현한 거 같아 죄송합니다만 좀 안타까워서 길게 주절거렸어요.
죄송 =ㅅ=

5개의 좋아요

댓글 감사합니다.

지금 배우고 있는 부분이라 이해가 많이 부족한게 사실입니다.
소스가 길어서 잘 간추려야 할 것 같은데
내용을 잘 모르면서 질문은 해야되고, 어떤 포인트에서 막히는 건지 잘 몰랐습니다.
(질문해야하는데 난처했습니다.)

잘 배웠습니다.
잘 가르쳐주시고,
피드백 감사합니다.

4개의 좋아요