안녕하세요.
WPF에서 리소스를 활용한 테마 변경 방법에 대한 질문입니다.
제가 사용한 방법은 동일한 x:Key
를 갖는 2개 이상의 리소스 테마를 구현하고 이 리소스 테마를 Runtime 상에서 바꾸는 방식인데요.
제가 궁금한 점은 MergedDictionaries 컬렉션에서 추가하고 제거하는 것이 올바른 방법인지, 그리고 다른 방법에 대해서도 알고싶습니다.
아래는 제가 구현한 방식의 일부분입니다.
각각의 테마 파일에 같은 x:Key
를 가진 리소스를 정의했습니다.
BrushResource.Dark.xaml
<SolidColorBrush Color="#FFEB33" x:Key="BRUSH.POINT"/>
<SolidColorBrush Color="#222222" x:Key="BRUSH.BG.01"/>
...
BrushResource.White.xaml
<SolidColorBrush Color="#FFEB33" x:Key="BRUSH.POINT"/>
<SolidColorBrush Color="#FFFFFF" x:Key="BRUSH.BG.01"/>
...
그리고 리소스를 DynamicResource 형식으로 모든 화면에서 사용하도록 하고 있습니다.
<Border Background="{DynamicResource BRUSH.POINT}"/>
마지막으로 실시간 테마 변경을 위한 Add/Remove 기능까지 구현했습니다.
private ResourceDictionary Dark;
private ResourceDictionary White;
private void StartApp()
{
Dark = new ResourceDictionary { Source = new Uri("...Dark.xaml"); };
White = new ResourceDictionary { Source = new Uri("...White.xaml"); };
App.Current.Resources.MergedDictionaries.Add(Dark);
}
private void TemeChange()
{
if (App.Current.Resources.MergedDictionaries.Contains(Dark))
{
App.Current.Resources.MergedDictionaries.Remove(Dark);
App.Current.Resources.MergedDictionaries.Add(White);
}
else if (App.Current.Resources.MergedDictionaries.Contains(White))
{
App.Current.Resources.MergedDictionaries.Remove(White);
App.Current.Resources.MergedDictionaries.Add(Dark);
}
}
보시는 바와 같이 직접 MergedDictionaries 컬렉션에서 직접 교체하는 방식으로 구현되어있습니다.
기능상으로는 문제가 없어보이지만 이 방법이 DynamicResource를 활용하기 위한 올바른 방법인지 잘 모르겠습니다.
읽어주셔서 감사합니다.
4개의 좋아요
런타임중에 테마를 바꿔야한다면 이 방법도 현명하다고 생각합니다.
5개의 좋아요
이 방법 말고 다른 효율적인 방법이 있나요? 저도 유사하게 썼던 기억입니다.
3개의 좋아요
테마를 지원하는 ModernWpf의 경우에도 유사하게 코딩되었음을 알 수 있습니다.
GitHub - Kinnara/ModernWpf: Modern styles and controls for your WPF applications
internal void ApplyApplicationTheme(ApplicationTheme theme)
{
int targetIndex = DesignMode.DesignModeEnabled ? 1 : 0;
if (SystemParameters.HighContrast)
{
EnsureHighContrastResources();
if (IsMerged(_highContrastResources))
{
if (CanBeAccessedAcrossThreads)
{
RefreshHighContrastResources();
}
}
else
{
MergedDictionaries.InsertOrReplace(targetIndex, _highContrastResources);
MergedDictionaries.RemoveIfNotNull(_lightResources);
MergedDictionaries.RemoveIfNotNull(_darkResources);
}
}
else
{
if (theme == ApplicationTheme.Light)
{
EnsureLightResources();
MergedDictionaries.InsertOrReplace(targetIndex, _lightResources);
MergedDictionaries.RemoveIfNotNull(_darkResources);
}
else if (theme == ApplicationTheme.Dark)
{
EnsureDarkResources();
MergedDictionaries.InsertOrReplace(targetIndex, _darkResources);
MergedDictionaries.RemoveIfNotNull(_lightResources);
}
else
{
throw new ArgumentOutOfRangeException(nameof(theme));
}
MergedDictionaries.RemoveIfNotNull(_highContrastResources);
}
Debug.Assert(MergedThemeDictionaryCount == 1);
}
...
...
2개의 좋아요
@dimohy @SangHyeon.Kim
이 앱도 다음과 같이 테마를 사용하네요!
public void Execute(ThemeType newTheme, bool followSystemTheme = false)
{
ForkSettings.Default.Theme = newTheme;
ForkSettings.Default.FollowSystemTheme = followSystemTheme;
App.RefreshWindowBorderBrush();
ResourceDictionary resourceDictionary = Application.Current.Resources.MergedDictionaries.Where((ResourceDictionary rd) => rd.Source != null).FirstOrDefault((ResourceDictionary rd) => Regex.Match(rd.Source.OriginalString, "(\\/Fork;component\\/Theme\\/Generic\\.)((Light)|(Dark))").Success);
ResourceDictionary item = new ResourceDictionary
{
Source = newTheme.ResourceUri()
};
Application.Current.Resources.MergedDictionaries.Add(item);
if (resourceDictionary != null)
{
Application.Current.Resources.MergedDictionaries.Remove(resourceDictionary);
}
NotificationCenter.Current.RaiseApplicationThemeChanged(this, newTheme);
}