본문 바로가기

프로그래밍/WPF

[WPF 이야기] 01.StringFormat

앞으로 1주일에 한번 정도씩 WPF에 대해서 간단하게 이야기를 써 보려고 한다.

내용은 아마도… 쉽지만 놓치면 아쉬운 정보들이 그 대상이 될 듯하다.

사실 한국에서는 WPF도 그렇도 닷넷도 사용자가 많이 없지만, 누군가에게 도움이 되었으면 좋겠다.


'StringFormat'은 원본이 되는 소스의 값을 특정 문자열 양식으로 변경해서 표현해주는 방법이다.

예를 들어서 숫자 값에 천단위 구분자를 추가해서 보여준다거나 (1000 -> 1,000), 날짜를 특정 포맷으로 표현한다거나 (DateTime -> 2019년12월02일 05시47분30초) 또는 숫자뒤에 통화 정보를 붙여서 가격처럼 보이게(300 -> 300원) 하는 예를

들 수 있다.

 

그럼 이러한 방법을 StringFormat을 쓰지 않고 표현할 수는 있을까? 물론 가능하다.

 

접근법이 2개가 될 것 같은데 아래와 같다.

- 첫번째는 ViewModel에서 View가 원하는 양식으로 값을 변환해서 바인딩하는 방법이 있을 것이고,

- 두번째는 'Converter' 를 사용해서 View에서 값을 변경할 수도 있다

상황에 따른 예외가 있겠지만 내가 생각했을 때는 두 방법 모두 좋은 방법은 아닌 것 같다.

 

ViewModel에서 View가 원하는 양식으로 바인딩을 하는 경우는 ViewModel이 너무 View에 의존적으로 개발 되어야 하기 때문에 좋지 않다. 혹시 해당 ViewModel 다른 View를 통해서 표현되어야 한다면 어떻할까? 아마 ViewModel에는 또 다른 View를 위해서 새로운 변환 코드가 추가되어야 할 수도 있다.

 

Converter를 사용하는 경우는 View에서 ViewModel의 값을 원하는 양식으로 변경은 가능하겠지만 매번 이를 위한 Converter 클래스가 추가되어야 한다. 혹시나 나중에 다국어 기능이 지원되어야 한다면 Convter에 들어가는 하드 코딩된 문자열은 어떻게 처리할 것인가?

아쉽지만 이것도 좋은 방법은 아닌것 같다.

나의 경우는 Converter는 주로 타입간의 변환이나 특정 값 계산을 할 때 사용하고 있다. Conveter는 다음이야기에서 한번 다루어 보자.

 

예상된 답이겠지만 앞에서 소개한 StringFormat을 이용하면 이 문제를 쉽게 해결할 수 있다.

C#을 사용하면서 DateTime 값을 특정 양식의 문자로 변경하기 위해서 StringFormat을 사용해 봤을 건데 그와 같다고 보면 된다.

예를 들어서 'DateTime' 타입의 날짜 정보를 View에 특정 양식별로 표시한다고 해 보자.

아무런 변경 없이 DateTime 값을 그대로 TextBlock에 바인딩하면 '12/1/2019 10:41:41 PM' 으로 표시된다.

이때 View와 ViewModel 코드는 아래와 같다. 'CurrentTime' 이름의 datetime 값을 그대로 TextBlock에 바인딩하고 있다.

 

public class MainViewModel : ViewModelBase
{
    private DateTime _currentTime;

    public DateTime CurrentTime
    {
        get { return _currentTime; }
        set { SetProperty(ref _currentTime, value); }
    }

    public MainViewModel()
    {
        CurrentTime = DateTime.Now;
    }
}

 

<StackPanel Orientation="Vertical" Margin="20" TextElement.FontSize="20">
    <TextBlock Text="{Binding CurrentTime}"/>
</StackPanel>

 

그럼 날짜 정보를 'yyyy년MM월dd일 HH시mm분ss초'로 표시해야 한다고 해보자.

XAML 코드에서 바인딩 뒤에 StringFormat을 추가해주면 간단히 구현 가능하다.

 

<StackPanel Orientation="Vertical" Margin="20" TextElement.FontSize="20">
    <TextBlock Text="{Binding CurrentTime}"/>
    <TextBlock Text="{Binding CurrentTime, StringFormat=yyyy년MM월dd일 HH시mm분ss초}"/> <!--yyyy년MM월dd일 HH시mm분ss초-->
</StackPanel>

 

여기서 한 단계 더 나아갈 수 있는 방법은, StringFormat을 별도의 리소스로 분리하는 것이다. 그래서 다른 View에서 동일한 변경이 필요할 때, 분리된 리소스를 재사용할 수 있다.

방법은 'Properties -> Resources'에 StringFormat을 정의하고, View에서 이 값을 불러오면 된다.

 

Name: MyTimeFormat
Value: {0:yyyy년MM월dd일}

 

위 코드를 보면 처음 XAML 코드와 다르게 '0:'으로 시작하는 것을 볼 수 있는데, 이것은 첫번째로 바인딩 되는 값이라는 뜻이다.

나중에 멀티바인딩에 대해서 이야기 할 때 나오겠지만, 바인딩되는 값이 2개 이상이면, {0}, {1}, {2} 등으로 표시할 수 있다.

이렇게 리소스를 정의했으면 View코드에서 불러와서 사용하면 된다.

 

<!--Properties 경로를 참조하기 위해서 정보 추가-->
<!--YourProjectName에는 실제 프로젝트 이름을 추가-->
xmlns:p="clr-namespace:YourProjectName.Properties" 

<StackPanel Orientation="Vertical" Margin="20" TextElement.FontSize="20">
    <TextBlock Text="{Binding CurrentTime}"/>
    <TextBlock Text="{Binding CurrentTime, StringFormat=yyyy년MM월dd일 HH시mm분ss초}"/> <!--yyyy년MM월dd일 HH시mm분ss초-->
    <TextBlock Text="{Binding CurrentTime, StringFormat={x:Static p:Resources.MyTimeFormat}}"/> <!--Properties에 정의된 MyTimeFormat을 StringFormat으로 사용-->
</StackPanel>

 

마지막으로 숫자값을 천단위로 구분된 가격정보로 표시하는 예를 보여주고 마무리하자.

 

Name: MyCurrencyFormat
Value: {0:N0}원

 

private int _currentBudget;
public int CurrentBudget
{
    get { return _currentBudget; }
    set { SetProperty(ref _currentBudget, value); }
}

 

<TextBlock Text="{Binding CurrentBudget, StringFormat={x:Static p:Resources.MyCurrencyFormat}}"/>

전체 소스 경로: Link