[feat] WPF 회원관리 애플리케이션 구현
- .NET 8.0 WPF 프로젝트 생성 - MVVM 패턴 적용 (CommunityToolkit.Mvvm) - 의존성 주입 구현 (Microsoft.Extensions.DependencyInjection) - 로그인/회원관리/회원등록 화면 구현 - 테마 전환 기능 (Dark/Light) - 다국어 지원 (한국어/영어) - 세련된 로그인 UI 디자인 - CLAUDE.md 및 History.md 문서화 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
60
CLAUDE.md
Normal file
60
CLAUDE.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# CLAUDE.md
|
||||
|
||||
이 파일은 Claude Code (claude.ai/code)가 이 저장소에서 코드 작업을 할 때 참고하는 가이드입니다.
|
||||
|
||||
## 프로젝트 개요
|
||||
|
||||
EduToy는 WPF(Windows Presentation Foundation) 기초를 학습하기 위한 교육용 프로젝트입니다. "WPF 기초를 위한 토이프로젝트"라는 이름으로도 불립니다.
|
||||
|
||||
## 빌드 및 실행
|
||||
|
||||
```bash
|
||||
# 프로젝트 빌드
|
||||
cd src/WPFBeginner
|
||||
dotnet build
|
||||
|
||||
# 프로젝트 실행
|
||||
dotnet run
|
||||
```
|
||||
|
||||
## 기술 스택
|
||||
|
||||
- **.NET 8.0** - Windows WPF 애플리케이션
|
||||
- **CommunityToolkit.Mvvm 8.2.2** - MVVM 패턴 구현
|
||||
- **Microsoft.Extensions.DependencyInjection 8.0.0** - 의존성 주입
|
||||
|
||||
## 프로젝트 아키텍처
|
||||
|
||||
```
|
||||
src/WPFBeginner/
|
||||
├── Models/ # 데이터 모델 (Member, AppSettings)
|
||||
├── ViewModels/ # MVVM ViewModel (LoginViewModel, MainViewModel, RegistViewModel)
|
||||
├── Views/ # XAML Window (LoginWindow, MainWindow, RegistWindow)
|
||||
├── Controls/ # 재사용 가능한 UserControl (InputPanel)
|
||||
├── Services/ # 비즈니스 로직 서비스 (SettingsService, MemberService)
|
||||
├── Converters/ # WPF 값 변환기
|
||||
└── Resources/
|
||||
├── Themes/ # 테마 ResourceDictionary (DefaultTheme, LightTheme)
|
||||
└── Languages/ # 다국어 리소스 (ko-KR, en-US)
|
||||
```
|
||||
|
||||
## MVVM 패턴
|
||||
|
||||
- **View**: XAML만 사용, 코드 비하인드에 비즈니스 로직 없음
|
||||
- **ViewModel**: CommunityToolkit.Mvvm의 `[ObservableProperty]`, `[RelayCommand]` 사용
|
||||
- **DI**: App.xaml.cs에서 ServiceCollection으로 서비스 및 ViewModel 등록
|
||||
|
||||
## 주요 기능
|
||||
|
||||
- **로그인**: appsettings.json의 자격 증명으로 인증 (기본: admin/0000)
|
||||
- **회원 관리**: DataGrid로 CRUD 작업
|
||||
- **테마 전환**: 어두운 테마(Default) / 밝은 테마(Light)
|
||||
- **다국어 지원**: 한국어(ko-KR) / 영어(en-US) 동적 전환
|
||||
|
||||
## 설정 파일
|
||||
|
||||
`appsettings.json`: 로그인 정보, 언어, 테마, 기본 회원 데이터 저장
|
||||
|
||||
## 언어
|
||||
|
||||
프로젝트 문서 및 UI는 한국어를 기본으로 사용합니다.
|
||||
29
EduToy.sln
Normal file
29
EduToy.sln
Normal file
@@ -0,0 +1,29 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.2.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFBeginner", "src\WPFBeginner\WPFBeginner.csproj", "{20E73DF0-D43D-8913-E28F-6960CD8A512D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{20E73DF0-D43D-8913-E28F-6960CD8A512D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{20E73DF0-D43D-8913-E28F-6960CD8A512D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{20E73DF0-D43D-8913-E28F-6960CD8A512D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{20E73DF0-D43D-8913-E28F-6960CD8A512D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{20E73DF0-D43D-8913-E28F-6960CD8A512D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {9791FF85-3B94-4645-B4A9-22D1B17C40A2}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
203
History.md
Normal file
203
History.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# 프로젝트 개발 히스토리
|
||||
|
||||
이 문서는 EduToy WPF 프로젝트의 개발 과정을 기록합니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 프로젝트 분석 및 CLAUDE.md 생성
|
||||
|
||||
### 요청
|
||||
- 코드베이스 분석 및 CLAUDE.md 파일 생성
|
||||
|
||||
### 작업 내용
|
||||
- 프로젝트 구조 탐색
|
||||
- "EduToy" - WPF 교육용 프로젝트로 확인
|
||||
- docs/sampleApp/WPFBeginner.exe (컴파일된 바이너리) 발견
|
||||
- CLAUDE.md 파일 영문으로 최초 작성
|
||||
|
||||
---
|
||||
|
||||
## 2. 한국어 전환 및 CLAUDE.md 번역
|
||||
|
||||
### 요청
|
||||
- 모든 대화를 한글로 진행
|
||||
- CLAUDE.md를 한글로 번역
|
||||
|
||||
### 작업 내용
|
||||
- CLAUDE.md 한국어로 번역 완료
|
||||
- 프로젝트 문서화 한국어 기준으로 변경
|
||||
|
||||
---
|
||||
|
||||
## 3. WPF 애플리케이션 구현
|
||||
|
||||
### 요청
|
||||
- PowerPoint 파일 (WPF 기초를 위한 토이프로젝트.pptx) 분석
|
||||
- sampleApp 참고하여 프로그램 구현
|
||||
|
||||
### 기술 스택
|
||||
- **.NET 8.0** Windows WPF
|
||||
- **CommunityToolkit.Mvvm 8.2.2** - MVVM 패턴
|
||||
- **Microsoft.Extensions.DependencyInjection 8.0.0** - DI
|
||||
|
||||
### 구현된 구조
|
||||
```
|
||||
src/WPFBeginner/
|
||||
├── Models/
|
||||
│ ├── Member.cs
|
||||
│ └── AppSettings.cs
|
||||
├── ViewModels/
|
||||
│ ├── LoginViewModel.cs
|
||||
│ ├── MainViewModel.cs
|
||||
│ └── RegistViewModel.cs
|
||||
├── Views/
|
||||
│ ├── LoginWindow.xaml / .cs
|
||||
│ ├── MainWindow.xaml / .cs
|
||||
│ └── RegistWindow.xaml / .cs
|
||||
├── Controls/
|
||||
│ └── InputPanel.xaml / .cs
|
||||
├── Services/
|
||||
│ ├── SettingsService.cs
|
||||
│ └── MemberService.cs
|
||||
├── Converters/
|
||||
│ └── BoolToVisibilityConverter.cs
|
||||
└── Resources/
|
||||
├── Themes/
|
||||
│ ├── DefaultTheme.xaml
|
||||
│ └── LightTheme.xaml
|
||||
└── Languages/
|
||||
├── ko-KR.xaml
|
||||
└── en-US.xaml
|
||||
```
|
||||
|
||||
### 주요 기능
|
||||
- 로그인 (appsettings.json 자격 증명)
|
||||
- 회원 관리 (DataGrid CRUD)
|
||||
- 테마 전환 (Dark/Light)
|
||||
- 다국어 지원 (한국어/영어)
|
||||
|
||||
---
|
||||
|
||||
## 4. 검색 영역 짤림 현상 수정
|
||||
|
||||
### 요청
|
||||
- 창 크기 줄였을 때 검색 영역이 짤리는 현상 수정
|
||||
- 엔터키로 검색 가능하도록 기능 개선
|
||||
|
||||
### 해결 방법
|
||||
- MinWidth="700" MinHeight="400" 설정
|
||||
- SearchTextBox_KeyDown 이벤트 핸들러 추가
|
||||
|
||||
### 수정 파일
|
||||
- `MainWindow.xaml` - MinWidth/MinHeight 추가
|
||||
- `MainWindow.xaml.cs` - KeyDown 이벤트 처리
|
||||
```csharp
|
||||
private void SearchTextBox_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter && DataContext is MainViewModel viewModel)
|
||||
{
|
||||
viewModel.SearchCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 로그인 화면 디자인 개선
|
||||
|
||||
### 요청
|
||||
- 가로 스크롤 디자인 개선
|
||||
- 로그인 화면을 세련되게 변경
|
||||
|
||||
### 적용된 디자인
|
||||
- 그라데이션 배경 (#1a1a2e → #16213e)
|
||||
- 둥근 모서리 (CornerRadius="12")
|
||||
- 그림자 효과 (DropShadowEffect)
|
||||
- 로고 아이콘 (원형 그라데이션)
|
||||
- 모던한 입력 필드 스타일
|
||||
- 그라데이션 로그인 버튼
|
||||
|
||||
### 수정 파일
|
||||
- `LoginWindow.xaml` - 전체 디자인 재구성
|
||||
|
||||
---
|
||||
|
||||
## 6. 로그인 화면 짤림 현상 수정
|
||||
|
||||
### 요청
|
||||
- 로그인 화면 위아래 짤림 현상 수정
|
||||
|
||||
### 해결 방법
|
||||
- 외부 Border에 Margin="15" 추가
|
||||
- 창 크기 500x420 → 540x440으로 확대
|
||||
|
||||
### 수정 파일
|
||||
- `LoginWindow.xaml`
|
||||
```xml
|
||||
<Window Height="540" Width="440">
|
||||
<Border Margin="15" CornerRadius="12">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="15" ShadowDepth="0" Opacity="0.5"/>
|
||||
</Border.Effect>
|
||||
...
|
||||
</Border>
|
||||
</Window>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 테마 버튼 동작 수정
|
||||
|
||||
### 요청
|
||||
- 테마 버튼이 동작하지 않는 문제 수정
|
||||
- 로그인 화면 제외 이전 디자인 유지
|
||||
|
||||
### 문제 원인
|
||||
- MainWindow.xaml에 하드코딩된 색상 사용
|
||||
- DynamicResource 대신 직접 색상값 (#1a1a2e 등) 사용
|
||||
|
||||
### 해결 방법
|
||||
- MainWindow.xaml을 DynamicResource 사용하도록 복원
|
||||
- LoginWindow.xaml은 새 디자인 유지 (하드코딩)
|
||||
|
||||
### 수정 파일
|
||||
- `MainWindow.xaml`
|
||||
```xml
|
||||
<Window Background="{DynamicResource WindowBackgroundBrush}">
|
||||
<Border Background="{DynamicResource SecondaryBrush}">
|
||||
<DataGrid Style="{DynamicResource DefaultDataGridStyle}">
|
||||
```
|
||||
|
||||
### 테마 전환 구조
|
||||
1. **MainWindow.xaml** - 라디오 버튼 Command 바인딩
|
||||
2. **MainViewModel.cs** - SetDefaultThemeCommand, SetLightThemeCommand
|
||||
3. **SettingsService.cs** - ApplyTheme() 메서드로 ResourceDictionary 교체
|
||||
|
||||
---
|
||||
|
||||
## 최종 결과
|
||||
|
||||
### 완료된 기능
|
||||
- ✅ MVVM 패턴 기반 WPF 애플리케이션
|
||||
- ✅ 의존성 주입 (DI) 구현
|
||||
- ✅ 로그인/회원관리 기능
|
||||
- ✅ 테마 전환 (Dark/Light)
|
||||
- ✅ 다국어 지원 (한국어/영어)
|
||||
- ✅ 세련된 로그인 화면 디자인
|
||||
- ✅ 반응형 레이아웃 (MinWidth/MinHeight)
|
||||
- ✅ 엔터키 검색 기능
|
||||
|
||||
### 빌드 및 실행
|
||||
```bash
|
||||
cd src/WPFBeginner
|
||||
dotnet build
|
||||
dotnet run
|
||||
```
|
||||
|
||||
### 기본 로그인 정보
|
||||
- ID: admin
|
||||
- Password: 0000
|
||||
|
||||
---
|
||||
|
||||
*마지막 업데이트: 2025-12-29*
|
||||
16
src/WPFBeginner/App.xaml
Normal file
16
src/WPFBeginner/App.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<Application x:Class="WPFBeginner.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:WPFBeginner"
|
||||
xmlns:converters="clr-namespace:WPFBeginner.Converters">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Resources/Themes/DefaultTheme.xaml"/>
|
||||
<ResourceDictionary Source="Resources/Languages/ko-KR.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
52
src/WPFBeginner/App.xaml.cs
Normal file
52
src/WPFBeginner/App.xaml.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Windows;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using WPFBeginner.Services;
|
||||
using WPFBeginner.ViewModels;
|
||||
using WPFBeginner.Views;
|
||||
|
||||
namespace WPFBeginner;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
private static IServiceProvider? _serviceProvider;
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
_serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Load settings first
|
||||
var settingsService = _serviceProvider.GetRequiredService<ISettingsService>();
|
||||
settingsService.Load();
|
||||
|
||||
// Show login window
|
||||
var loginWindow = new LoginWindow();
|
||||
loginWindow.Show();
|
||||
}
|
||||
|
||||
private static void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Services
|
||||
services.AddSingleton<ISettingsService, SettingsService>();
|
||||
services.AddSingleton<IMemberService, MemberService>();
|
||||
|
||||
// ViewModels
|
||||
services.AddTransient<LoginViewModel>();
|
||||
services.AddTransient<MainViewModel>();
|
||||
services.AddTransient<RegistViewModel>();
|
||||
}
|
||||
|
||||
public static T GetService<T>() where T : class
|
||||
{
|
||||
if (_serviceProvider == null)
|
||||
{
|
||||
throw new InvalidOperationException("Service provider is not initialized.");
|
||||
}
|
||||
|
||||
return _serviceProvider.GetRequiredService<T>();
|
||||
}
|
||||
}
|
||||
|
||||
10
src/WPFBeginner/AssemblyInfo.cs
Normal file
10
src/WPFBeginner/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly:ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
22
src/WPFBeginner/Controls/InputPanel.xaml
Normal file
22
src/WPFBeginner/Controls/InputPanel.xaml
Normal file
@@ -0,0 +1,22 @@
|
||||
<UserControl x:Class="WPFBeginner.Controls.InputPanel"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Name="Root">
|
||||
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Label Content="{Binding LabelText, ElementName=Root}"
|
||||
Style="{DynamicResource DefaultLabelStyle}"/>
|
||||
<TextBlock Text="*"
|
||||
Foreground="{DynamicResource ErrorBrush}"
|
||||
Visibility="{Binding IsRequired, ElementName=Root, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Margin="2,0,0,0"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<TextBox Text="{Binding Text, ElementName=Root, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{DynamicResource DefaultTextBoxStyle}"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
53
src/WPFBeginner/Controls/InputPanel.xaml.cs
Normal file
53
src/WPFBeginner/Controls/InputPanel.xaml.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace WPFBeginner.Controls;
|
||||
|
||||
public partial class InputPanel : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty LabelTextProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(LabelText),
|
||||
typeof(string),
|
||||
typeof(InputPanel),
|
||||
new PropertyMetadata(string.Empty));
|
||||
|
||||
public static readonly DependencyProperty TextProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(Text),
|
||||
typeof(string),
|
||||
typeof(InputPanel),
|
||||
new FrameworkPropertyMetadata(
|
||||
string.Empty,
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
||||
|
||||
public static readonly DependencyProperty IsRequiredProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(IsRequired),
|
||||
typeof(bool),
|
||||
typeof(InputPanel),
|
||||
new PropertyMetadata(false));
|
||||
|
||||
public string LabelText
|
||||
{
|
||||
get => (string)GetValue(LabelTextProperty);
|
||||
set => SetValue(LabelTextProperty, value);
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public bool IsRequired
|
||||
{
|
||||
get => (bool)GetValue(IsRequiredProperty);
|
||||
set => SetValue(IsRequiredProperty, value);
|
||||
}
|
||||
|
||||
public InputPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
26
src/WPFBeginner/Converters/BoolToVisibilityConverter.cs
Normal file
26
src/WPFBeginner/Converters/BoolToVisibilityConverter.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace WPFBeginner.Converters;
|
||||
|
||||
public class BoolToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return boolValue ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is Visibility visibility)
|
||||
{
|
||||
return visibility == Visibility.Visible;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
15
src/WPFBeginner/Models/AppSettings.cs
Normal file
15
src/WPFBeginner/Models/AppSettings.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace WPFBeginner.Models;
|
||||
|
||||
public class AppSettings
|
||||
{
|
||||
public LoginSettings Login { get; set; } = new();
|
||||
public string Language { get; set; } = "ko-KR";
|
||||
public string Theme { get; set; } = "DefaultTheme";
|
||||
public List<Member> DefaultMembers { get; set; } = new();
|
||||
}
|
||||
|
||||
public class LoginSettings
|
||||
{
|
||||
public string User { get; set; } = "admin";
|
||||
public string Password { get; set; } = "0000";
|
||||
}
|
||||
33
src/WPFBeginner/Models/Member.cs
Normal file
33
src/WPFBeginner/Models/Member.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace WPFBeginner.Models;
|
||||
|
||||
public partial class Member : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private string _name = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _call = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _eMail = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _no = string.Empty;
|
||||
|
||||
public Member() { }
|
||||
|
||||
public Member(string name, string call, string email, string no)
|
||||
{
|
||||
Name = name;
|
||||
Call = call;
|
||||
EMail = email;
|
||||
No = no;
|
||||
}
|
||||
|
||||
public Member Clone()
|
||||
{
|
||||
return new Member(Name, Call, EMail, No);
|
||||
}
|
||||
}
|
||||
53
src/WPFBeginner/Resources/Languages/en-US.xaml
Normal file
53
src/WPFBeginner/Resources/Languages/en-US.xaml
Normal file
@@ -0,0 +1,53 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<!-- Login Window -->
|
||||
<system:String x:Key="Login_Title">Login</system:String>
|
||||
<system:String x:Key="Login_Welcome">Welcome</system:String>
|
||||
<system:String x:Key="Login_UserId">User ID</system:String>
|
||||
<system:String x:Key="Login_Password">Password</system:String>
|
||||
<system:String x:Key="Login_LoginButton">Login</system:String>
|
||||
<system:String x:Key="Login_Error">Invalid ID or Password</system:String>
|
||||
|
||||
<!-- Main Window -->
|
||||
<system:String x:Key="Main_Title">Member Management</system:String>
|
||||
<system:String x:Key="Main_Search">Search</system:String>
|
||||
<system:String x:Key="Main_SearchPlaceholder">Enter search keyword</system:String>
|
||||
<system:String x:Key="Main_Add">Add</system:String>
|
||||
<system:String x:Key="Main_Delete">Delete</system:String>
|
||||
<system:String x:Key="Main_Reset">Reset</system:String>
|
||||
<system:String x:Key="Main_Settings">Settings</system:String>
|
||||
<system:String x:Key="Main_NoResults">No results found</system:String>
|
||||
|
||||
<!-- Main Window - DataGrid -->
|
||||
<system:String x:Key="Main_Column_Name">Name</system:String>
|
||||
<system:String x:Key="Main_Column_Call">Phone</system:String>
|
||||
<system:String x:Key="Main_Column_Email">Email</system:String>
|
||||
<system:String x:Key="Main_Column_No">Employee No.</system:String>
|
||||
|
||||
<!-- Main Window - Settings -->
|
||||
<system:String x:Key="Settings_Language">Language</system:String>
|
||||
<system:String x:Key="Settings_Korean">한국어</system:String>
|
||||
<system:String x:Key="Settings_English">English</system:String>
|
||||
<system:String x:Key="Settings_Theme">Theme</system:String>
|
||||
<system:String x:Key="Settings_DefaultTheme">Default (Dark)</system:String>
|
||||
<system:String x:Key="Settings_LightTheme">Light</system:String>
|
||||
|
||||
<!-- Registration Window -->
|
||||
<system:String x:Key="Regist_Title">Member Registration</system:String>
|
||||
<system:String x:Key="Regist_Name">Name</system:String>
|
||||
<system:String x:Key="Regist_Call">Phone</system:String>
|
||||
<system:String x:Key="Regist_Email">Email</system:String>
|
||||
<system:String x:Key="Regist_No">Employee No.</system:String>
|
||||
<system:String x:Key="Regist_Register">Register</system:String>
|
||||
<system:String x:Key="Regist_Cancel">Cancel</system:String>
|
||||
<system:String x:Key="Regist_NameRequired">Name is required</system:String>
|
||||
<system:String x:Key="Regist_EmailRequired">Email is required</system:String>
|
||||
|
||||
<!-- Common -->
|
||||
<system:String x:Key="Common_Close">Close</system:String>
|
||||
<system:String x:Key="Common_OK">OK</system:String>
|
||||
<system:String x:Key="Common_Cancel">Cancel</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
53
src/WPFBeginner/Resources/Languages/ko-KR.xaml
Normal file
53
src/WPFBeginner/Resources/Languages/ko-KR.xaml
Normal file
@@ -0,0 +1,53 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<!-- Login Window -->
|
||||
<system:String x:Key="Login_Title">로그인</system:String>
|
||||
<system:String x:Key="Login_Welcome">환영합니다</system:String>
|
||||
<system:String x:Key="Login_UserId">아이디</system:String>
|
||||
<system:String x:Key="Login_Password">비밀번호</system:String>
|
||||
<system:String x:Key="Login_LoginButton">로그인</system:String>
|
||||
<system:String x:Key="Login_Error">아이디 또는 비밀번호가 틀렸습니다</system:String>
|
||||
|
||||
<!-- Main Window -->
|
||||
<system:String x:Key="Main_Title">회원 관리</system:String>
|
||||
<system:String x:Key="Main_Search">검색</system:String>
|
||||
<system:String x:Key="Main_SearchPlaceholder">검색어를 입력하세요</system:String>
|
||||
<system:String x:Key="Main_Add">추가</system:String>
|
||||
<system:String x:Key="Main_Delete">삭제</system:String>
|
||||
<system:String x:Key="Main_Reset">초기화</system:String>
|
||||
<system:String x:Key="Main_Settings">설정</system:String>
|
||||
<system:String x:Key="Main_NoResults">검색 결과가 없습니다</system:String>
|
||||
|
||||
<!-- Main Window - DataGrid -->
|
||||
<system:String x:Key="Main_Column_Name">이름</system:String>
|
||||
<system:String x:Key="Main_Column_Call">연락처</system:String>
|
||||
<system:String x:Key="Main_Column_Email">이메일</system:String>
|
||||
<system:String x:Key="Main_Column_No">사번</system:String>
|
||||
|
||||
<!-- Main Window - Settings -->
|
||||
<system:String x:Key="Settings_Language">언어</system:String>
|
||||
<system:String x:Key="Settings_Korean">한국어</system:String>
|
||||
<system:String x:Key="Settings_English">English</system:String>
|
||||
<system:String x:Key="Settings_Theme">테마</system:String>
|
||||
<system:String x:Key="Settings_DefaultTheme">기본 (어두운)</system:String>
|
||||
<system:String x:Key="Settings_LightTheme">밝은</system:String>
|
||||
|
||||
<!-- Registration Window -->
|
||||
<system:String x:Key="Regist_Title">회원 등록</system:String>
|
||||
<system:String x:Key="Regist_Name">이름</system:String>
|
||||
<system:String x:Key="Regist_Call">연락처</system:String>
|
||||
<system:String x:Key="Regist_Email">이메일</system:String>
|
||||
<system:String x:Key="Regist_No">사번</system:String>
|
||||
<system:String x:Key="Regist_Register">등록</system:String>
|
||||
<system:String x:Key="Regist_Cancel">취소</system:String>
|
||||
<system:String x:Key="Regist_NameRequired">이름은 필수입니다</system:String>
|
||||
<system:String x:Key="Regist_EmailRequired">이메일은 필수입니다</system:String>
|
||||
|
||||
<!-- Common -->
|
||||
<system:String x:Key="Common_Close">닫기</system:String>
|
||||
<system:String x:Key="Common_OK">확인</system:String>
|
||||
<system:String x:Key="Common_Cancel">취소</system:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
192
src/WPFBeginner/Resources/Themes/DefaultTheme.xaml
Normal file
192
src/WPFBeginner/Resources/Themes/DefaultTheme.xaml
Normal file
@@ -0,0 +1,192 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!-- Colors -->
|
||||
<Color x:Key="PrimaryColor">#2D2D30</Color>
|
||||
<Color x:Key="SecondaryColor">#3E3E42</Color>
|
||||
<Color x:Key="AccentColor">#007ACC</Color>
|
||||
<Color x:Key="TextColor">#FFFFFF</Color>
|
||||
<Color x:Key="SubTextColor">#A0A0A0</Color>
|
||||
<Color x:Key="BorderColor">#555555</Color>
|
||||
<Color x:Key="ErrorColor">#FF5555</Color>
|
||||
<Color x:Key="SuccessColor">#55FF55</Color>
|
||||
|
||||
<!-- Brushes -->
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}"/>
|
||||
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource SecondaryColor}"/>
|
||||
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/>
|
||||
<SolidColorBrush x:Key="TextBrush" Color="{StaticResource TextColor}"/>
|
||||
<SolidColorBrush x:Key="SubTextBrush" Color="{StaticResource SubTextColor}"/>
|
||||
<SolidColorBrush x:Key="BorderBrush" Color="{StaticResource BorderColor}"/>
|
||||
<SolidColorBrush x:Key="ErrorBrush" Color="{StaticResource ErrorColor}"/>
|
||||
<SolidColorBrush x:Key="SuccessBrush" Color="{StaticResource SuccessColor}"/>
|
||||
|
||||
<!-- Window Background -->
|
||||
<SolidColorBrush x:Key="WindowBackgroundBrush" Color="{StaticResource PrimaryColor}"/>
|
||||
|
||||
<!-- Button Style -->
|
||||
<Style x:Key="PrimaryButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="15,8"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#1A8AD4"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#005A9E"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SecondaryButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="15,8"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#4E4E52"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- TextBox Style -->
|
||||
<Style x:Key="DefaultTextBoxStyle" TargetType="TextBox">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="8,6"/>
|
||||
<Setter Property="CaretBrush" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TextBox">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4">
|
||||
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsFocused" Value="True">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- PasswordBox Style -->
|
||||
<Style x:Key="DefaultPasswordBoxStyle" TargetType="PasswordBox">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="8,6"/>
|
||||
<Setter Property="CaretBrush" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="PasswordBox">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4">
|
||||
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsFocused" Value="True">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Label Style -->
|
||||
<Style x:Key="DefaultLabelStyle" TargetType="Label">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="Padding" Value="0,0,0,4"/>
|
||||
</Style>
|
||||
|
||||
<!-- DataGrid Style -->
|
||||
<Style x:Key="DefaultDataGridStyle" TargetType="DataGrid">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="RowBackground" Value="{StaticResource SecondaryBrush}"/>
|
||||
<Setter Property="AlternatingRowBackground" Value="{StaticResource PrimaryBrush}"/>
|
||||
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="HeadersVisibility" Value="Column"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="Padding" Value="10,8"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="0,0,1,1"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridCell">
|
||||
<Setter Property="Padding" Value="10,6"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#3E3E42"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!-- RadioButton Style -->
|
||||
<Style x:Key="DefaultRadioButtonStyle" TargetType="RadioButton">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="Margin" Value="0,4"/>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
192
src/WPFBeginner/Resources/Themes/LightTheme.xaml
Normal file
192
src/WPFBeginner/Resources/Themes/LightTheme.xaml
Normal file
@@ -0,0 +1,192 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!-- Colors -->
|
||||
<Color x:Key="PrimaryColor">#FFFFFF</Color>
|
||||
<Color x:Key="SecondaryColor">#F5F5F5</Color>
|
||||
<Color x:Key="AccentColor">#007ACC</Color>
|
||||
<Color x:Key="TextColor">#1E1E1E</Color>
|
||||
<Color x:Key="SubTextColor">#666666</Color>
|
||||
<Color x:Key="BorderColor">#CCCCCC</Color>
|
||||
<Color x:Key="ErrorColor">#D32F2F</Color>
|
||||
<Color x:Key="SuccessColor">#388E3C</Color>
|
||||
|
||||
<!-- Brushes -->
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}"/>
|
||||
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource SecondaryColor}"/>
|
||||
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/>
|
||||
<SolidColorBrush x:Key="TextBrush" Color="{StaticResource TextColor}"/>
|
||||
<SolidColorBrush x:Key="SubTextBrush" Color="{StaticResource SubTextColor}"/>
|
||||
<SolidColorBrush x:Key="BorderBrush" Color="{StaticResource BorderColor}"/>
|
||||
<SolidColorBrush x:Key="ErrorBrush" Color="{StaticResource ErrorColor}"/>
|
||||
<SolidColorBrush x:Key="SuccessBrush" Color="{StaticResource SuccessColor}"/>
|
||||
|
||||
<!-- Window Background -->
|
||||
<SolidColorBrush x:Key="WindowBackgroundBrush" Color="{StaticResource PrimaryColor}"/>
|
||||
|
||||
<!-- Button Style -->
|
||||
<Style x:Key="PrimaryButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="15,8"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#1A8AD4"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#005A9E"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SecondaryButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="15,8"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#E0E0E0"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- TextBox Style -->
|
||||
<Style x:Key="DefaultTextBoxStyle" TargetType="TextBox">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="8,6"/>
|
||||
<Setter Property="CaretBrush" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TextBox">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4">
|
||||
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsFocused" Value="True">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- PasswordBox Style -->
|
||||
<Style x:Key="DefaultPasswordBoxStyle" TargetType="PasswordBox">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="8,6"/>
|
||||
<Setter Property="CaretBrush" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="PasswordBox">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4">
|
||||
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsFocused" Value="True">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Label Style -->
|
||||
<Style x:Key="DefaultLabelStyle" TargetType="Label">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="Padding" Value="0,0,0,4"/>
|
||||
</Style>
|
||||
|
||||
<!-- DataGrid Style -->
|
||||
<Style x:Key="DefaultDataGridStyle" TargetType="DataGrid">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="RowBackground" Value="White"/>
|
||||
<Setter Property="AlternatingRowBackground" Value="{StaticResource SecondaryBrush}"/>
|
||||
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="HeadersVisibility" Value="Column"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="{StaticResource SecondaryBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="Padding" Value="10,8"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="0,0,1,1"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridCell">
|
||||
<Setter Property="Padding" Value="10,6"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#E8E8E8"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!-- RadioButton Style -->
|
||||
<Style x:Key="DefaultRadioButtonStyle" TargetType="RadioButton">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
||||
<Setter Property="Margin" Value="0,4"/>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
61
src/WPFBeginner/Services/MemberService.cs
Normal file
61
src/WPFBeginner/Services/MemberService.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using WPFBeginner.Models;
|
||||
|
||||
namespace WPFBeginner.Services;
|
||||
|
||||
public interface IMemberService
|
||||
{
|
||||
ObservableCollection<Member> Members { get; }
|
||||
void LoadDefaultMembers(List<Member> defaultMembers);
|
||||
void Add(Member member);
|
||||
void Remove(Member member);
|
||||
void Reset();
|
||||
IEnumerable<Member> Search(string keyword);
|
||||
}
|
||||
|
||||
public class MemberService : IMemberService
|
||||
{
|
||||
private readonly List<Member> _defaultMembers = new();
|
||||
public ObservableCollection<Member> Members { get; } = new();
|
||||
|
||||
public void LoadDefaultMembers(List<Member> defaultMembers)
|
||||
{
|
||||
_defaultMembers.Clear();
|
||||
_defaultMembers.AddRange(defaultMembers.Select(m => m.Clone()));
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Add(Member member)
|
||||
{
|
||||
Members.Add(member);
|
||||
}
|
||||
|
||||
public void Remove(Member member)
|
||||
{
|
||||
Members.Remove(member);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Members.Clear();
|
||||
foreach (var member in _defaultMembers)
|
||||
{
|
||||
Members.Add(member.Clone());
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Member> Search(string keyword)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
return Members;
|
||||
}
|
||||
|
||||
var lowerKeyword = keyword.ToLower();
|
||||
return Members.Where(m =>
|
||||
m.Name.ToLower().Contains(lowerKeyword) ||
|
||||
m.Call.ToLower().Contains(lowerKeyword) ||
|
||||
m.EMail.ToLower().Contains(lowerKeyword) ||
|
||||
m.No.ToLower().Contains(lowerKeyword));
|
||||
}
|
||||
}
|
||||
114
src/WPFBeginner/Services/SettingsService.cs
Normal file
114
src/WPFBeginner/Services/SettingsService.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
using WPFBeginner.Models;
|
||||
|
||||
namespace WPFBeginner.Services;
|
||||
|
||||
public interface ISettingsService
|
||||
{
|
||||
AppSettings Settings { get; }
|
||||
void Load();
|
||||
void Save();
|
||||
void SetLanguage(string language);
|
||||
void SetTheme(string theme);
|
||||
string CurrentLanguage { get; }
|
||||
string CurrentTheme { get; }
|
||||
}
|
||||
|
||||
public class SettingsService : ISettingsService
|
||||
{
|
||||
private readonly string _settingsPath;
|
||||
private AppSettings _settings = new();
|
||||
|
||||
public AppSettings Settings => _settings;
|
||||
public string CurrentLanguage => _settings.Language;
|
||||
public string CurrentTheme => _settings.Theme;
|
||||
|
||||
public SettingsService()
|
||||
{
|
||||
_settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "appsettings.json");
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_settingsPath))
|
||||
{
|
||||
var json = File.ReadAllText(_settingsPath);
|
||||
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||
_settings = JsonSerializer.Deserialize<AppSettings>(json, options) ?? new AppSettings();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_settings = new AppSettings();
|
||||
}
|
||||
|
||||
ApplyLanguage(_settings.Language);
|
||||
ApplyTheme(_settings.Theme);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
var json = JsonSerializer.Serialize(_settings, options);
|
||||
File.WriteAllText(_settingsPath, json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore save errors
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLanguage(string language)
|
||||
{
|
||||
_settings.Language = language;
|
||||
ApplyLanguage(language);
|
||||
Save();
|
||||
}
|
||||
|
||||
public void SetTheme(string theme)
|
||||
{
|
||||
_settings.Theme = theme;
|
||||
ApplyTheme(theme);
|
||||
Save();
|
||||
}
|
||||
|
||||
private void ApplyLanguage(string language)
|
||||
{
|
||||
var dict = new ResourceDictionary();
|
||||
var langFile = language == "en-US" ? "en-US" : "ko-KR";
|
||||
dict.Source = new Uri($"pack://application:,,,/Resources/Languages/{langFile}.xaml");
|
||||
|
||||
// Remove existing language dictionary
|
||||
var existingLang = Application.Current.Resources.MergedDictionaries
|
||||
.FirstOrDefault(d => d.Source?.OriginalString.Contains("/Languages/") == true);
|
||||
if (existingLang != null)
|
||||
{
|
||||
Application.Current.Resources.MergedDictionaries.Remove(existingLang);
|
||||
}
|
||||
|
||||
Application.Current.Resources.MergedDictionaries.Add(dict);
|
||||
}
|
||||
|
||||
private void ApplyTheme(string theme)
|
||||
{
|
||||
var dict = new ResourceDictionary();
|
||||
var themeFile = theme == "LightTheme" ? "LightTheme" : "DefaultTheme";
|
||||
dict.Source = new Uri($"pack://application:,,,/Resources/Themes/{themeFile}.xaml");
|
||||
|
||||
// Remove existing theme dictionary
|
||||
var existingTheme = Application.Current.Resources.MergedDictionaries
|
||||
.FirstOrDefault(d => d.Source?.OriginalString.Contains("/Themes/") == true);
|
||||
if (existingTheme != null)
|
||||
{
|
||||
Application.Current.Resources.MergedDictionaries.Remove(existingTheme);
|
||||
}
|
||||
|
||||
Application.Current.Resources.MergedDictionaries.Add(dict);
|
||||
}
|
||||
}
|
||||
62
src/WPFBeginner/ViewModels/LoginViewModel.cs
Normal file
62
src/WPFBeginner/ViewModels/LoginViewModel.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using WPFBeginner.Services;
|
||||
using WPFBeginner.Views;
|
||||
|
||||
namespace WPFBeginner.ViewModels;
|
||||
|
||||
public partial class LoginViewModel : ObservableObject
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _userId = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _password = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
public string WelcomeMessage => _settingsService.CurrentLanguage == "en-US"
|
||||
? "Welcome"
|
||||
: "환영합니다";
|
||||
|
||||
public LoginViewModel(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Login(Window window)
|
||||
{
|
||||
var settings = _settingsService.Settings.Login;
|
||||
|
||||
if (UserId == settings.User && Password == settings.Password)
|
||||
{
|
||||
HasError = false;
|
||||
ErrorMessage = string.Empty;
|
||||
|
||||
var mainWindow = new MainWindow();
|
||||
mainWindow.Show();
|
||||
window.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
HasError = true;
|
||||
ErrorMessage = _settingsService.CurrentLanguage == "en-US"
|
||||
? "Invalid ID or Password"
|
||||
: "아이디 또는 비밀번호가 틀렸습니다";
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Close(Window window)
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
165
src/WPFBeginner/ViewModels/MainViewModel.cs
Normal file
165
src/WPFBeginner/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using WPFBeginner.Models;
|
||||
using WPFBeginner.Services;
|
||||
using WPFBeginner.Views;
|
||||
|
||||
namespace WPFBeginner.ViewModels;
|
||||
|
||||
public partial class MainViewModel : ObservableObject
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IMemberService _memberService;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _searchKeyword = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private Member? _selectedMember;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<Member> _filteredMembers = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isKorean = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEnglish;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDefaultTheme = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLightTheme;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _searchResultMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasSearchResult = true;
|
||||
|
||||
public MainViewModel(ISettingsService settingsService, IMemberService memberService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_memberService = memberService;
|
||||
|
||||
// Load default members
|
||||
_memberService.LoadDefaultMembers(_settingsService.Settings.DefaultMembers);
|
||||
|
||||
// Set initial language/theme based on settings
|
||||
IsKorean = _settingsService.CurrentLanguage == "ko-KR";
|
||||
IsEnglish = _settingsService.CurrentLanguage == "en-US";
|
||||
IsDefaultTheme = _settingsService.CurrentTheme == "DefaultTheme";
|
||||
IsLightTheme = _settingsService.CurrentTheme == "LightTheme";
|
||||
|
||||
// Initialize filtered members
|
||||
RefreshFilteredMembers();
|
||||
}
|
||||
|
||||
private void RefreshFilteredMembers()
|
||||
{
|
||||
FilteredMembers = new ObservableCollection<Member>(_memberService.Members);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Search()
|
||||
{
|
||||
var results = _memberService.Search(SearchKeyword).ToList();
|
||||
FilteredMembers = new ObservableCollection<Member>(results);
|
||||
|
||||
if (results.Count == 0)
|
||||
{
|
||||
HasSearchResult = false;
|
||||
SearchResultMessage = _settingsService.CurrentLanguage == "en-US"
|
||||
? "No results found"
|
||||
: "검색 결과가 없습니다";
|
||||
|
||||
MessageBox.Show(SearchResultMessage,
|
||||
_settingsService.CurrentLanguage == "en-US" ? "Search" : "검색",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information);
|
||||
}
|
||||
else
|
||||
{
|
||||
HasSearchResult = true;
|
||||
SearchResultMessage = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AddMember()
|
||||
{
|
||||
var registWindow = new RegistWindow();
|
||||
registWindow.MemberAdded += OnMemberAdded;
|
||||
registWindow.ShowDialog();
|
||||
}
|
||||
|
||||
private void OnMemberAdded(object? sender, Member member)
|
||||
{
|
||||
_memberService.Add(member);
|
||||
RefreshFilteredMembers();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void DeleteMember()
|
||||
{
|
||||
if (SelectedMember != null)
|
||||
{
|
||||
_memberService.Remove(SelectedMember);
|
||||
RefreshFilteredMembers();
|
||||
SelectedMember = null;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Reset()
|
||||
{
|
||||
SearchKeyword = string.Empty;
|
||||
_memberService.Reset();
|
||||
RefreshFilteredMembers();
|
||||
HasSearchResult = true;
|
||||
SearchResultMessage = string.Empty;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SetKorean()
|
||||
{
|
||||
IsKorean = true;
|
||||
IsEnglish = false;
|
||||
_settingsService.SetLanguage("ko-KR");
|
||||
OnPropertyChanged(nameof(IsKorean));
|
||||
OnPropertyChanged(nameof(IsEnglish));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SetEnglish()
|
||||
{
|
||||
IsKorean = false;
|
||||
IsEnglish = true;
|
||||
_settingsService.SetLanguage("en-US");
|
||||
OnPropertyChanged(nameof(IsKorean));
|
||||
OnPropertyChanged(nameof(IsEnglish));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SetDefaultTheme()
|
||||
{
|
||||
IsDefaultTheme = true;
|
||||
IsLightTheme = false;
|
||||
_settingsService.SetTheme("DefaultTheme");
|
||||
OnPropertyChanged(nameof(IsDefaultTheme));
|
||||
OnPropertyChanged(nameof(IsLightTheme));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SetLightTheme()
|
||||
{
|
||||
IsDefaultTheme = false;
|
||||
IsLightTheme = true;
|
||||
_settingsService.SetTheme("LightTheme");
|
||||
OnPropertyChanged(nameof(IsDefaultTheme));
|
||||
OnPropertyChanged(nameof(IsLightTheme));
|
||||
}
|
||||
}
|
||||
93
src/WPFBeginner/ViewModels/RegistViewModel.cs
Normal file
93
src/WPFBeginner/ViewModels/RegistViewModel.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using WPFBeginner.Models;
|
||||
using WPFBeginner.Services;
|
||||
|
||||
namespace WPFBeginner.ViewModels;
|
||||
|
||||
public partial class RegistViewModel : ObservableObject
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _name = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _call = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _eMail = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _no = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
public event EventHandler<Member>? MemberAdded;
|
||||
|
||||
public RegistViewModel(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Register(Window window)
|
||||
{
|
||||
// Validate required fields
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
HasError = true;
|
||||
ErrorMessage = _settingsService.CurrentLanguage == "en-US"
|
||||
? "Name is required"
|
||||
: "이름은 필수입니다";
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(EMail))
|
||||
{
|
||||
HasError = true;
|
||||
ErrorMessage = _settingsService.CurrentLanguage == "en-US"
|
||||
? "Email is required"
|
||||
: "이메일은 필수입니다";
|
||||
return;
|
||||
}
|
||||
|
||||
HasError = false;
|
||||
ErrorMessage = string.Empty;
|
||||
|
||||
var member = new Member(Name, Call, EMail, No);
|
||||
MemberAdded?.Invoke(this, member);
|
||||
window.Close();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Cancel(Window window)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Minimize(Window window)
|
||||
{
|
||||
window.WindowState = WindowState.Minimized;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Maximize(Window window)
|
||||
{
|
||||
window.WindowState = window.WindowState == WindowState.Maximized
|
||||
? WindowState.Normal
|
||||
: WindowState.Maximized;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Close(Window window)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
198
src/WPFBeginner/Views/LoginWindow.xaml
Normal file
198
src/WPFBeginner/Views/LoginWindow.xaml
Normal file
@@ -0,0 +1,198 @@
|
||||
<Window x:Class="WPFBeginner.Views.LoginWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Title="{DynamicResource Login_Title}"
|
||||
Height="540" Width="440"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
ResizeMode="NoResize">
|
||||
|
||||
<Border Margin="15" CornerRadius="12">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="15" ShadowDepth="0" Opacity="0.5"/>
|
||||
</Border.Effect>
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#1a1a2e" Offset="0"/>
|
||||
<GradientStop Color="#16213e" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title Bar -->
|
||||
<Grid Grid.Row="0" Background="Transparent" MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1"
|
||||
Content="✕"
|
||||
Width="45" Height="35"
|
||||
Background="Transparent"
|
||||
Foreground="#808080"
|
||||
BorderThickness="0"
|
||||
FontSize="14"
|
||||
Cursor="Hand"
|
||||
Command="{Binding CloseCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="0,12,0,0">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#E81123"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Content -->
|
||||
<StackPanel Grid.Row="1" Margin="40,10,40,30" VerticalAlignment="Center">
|
||||
|
||||
<!-- Logo Icon -->
|
||||
<Border Width="70" Height="70"
|
||||
CornerRadius="35"
|
||||
Margin="0,0,0,20"
|
||||
HorizontalAlignment="Center">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||
<GradientStop Color="#0f3460" Offset="0"/>
|
||||
<GradientStop Color="#e94560" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock Text="W"
|
||||
FontSize="32"
|
||||
FontWeight="Bold"
|
||||
Foreground="White"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
<!-- Welcome Message -->
|
||||
<TextBlock Text="{DynamicResource Login_Welcome}"
|
||||
Foreground="White"
|
||||
FontSize="26"
|
||||
FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,0,0,5"/>
|
||||
|
||||
<TextBlock Text="WPF Beginner"
|
||||
Foreground="#808080"
|
||||
FontSize="13"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,0,0,25"/>
|
||||
|
||||
<!-- User ID -->
|
||||
<TextBlock Text="{DynamicResource Login_UserId}"
|
||||
Foreground="#a0a0a0"
|
||||
FontSize="12"
|
||||
Margin="5,0,0,6"/>
|
||||
<Border CornerRadius="8" Background="#0f3460" Margin="0,0,0,15">
|
||||
<TextBox Text="{Binding UserId, UpdateSourceTrigger=PropertyChanged}"
|
||||
Background="Transparent"
|
||||
Foreground="White"
|
||||
BorderThickness="0"
|
||||
Padding="12,10"
|
||||
FontSize="14"
|
||||
CaretBrush="White"/>
|
||||
</Border>
|
||||
|
||||
<!-- Password -->
|
||||
<TextBlock Text="{DynamicResource Login_Password}"
|
||||
Foreground="#a0a0a0"
|
||||
FontSize="12"
|
||||
Margin="5,0,0,6"/>
|
||||
<Border CornerRadius="8" Background="#0f3460" Margin="0,0,0,12">
|
||||
<PasswordBox x:Name="PasswordBox"
|
||||
Background="Transparent"
|
||||
Foreground="White"
|
||||
BorderThickness="0"
|
||||
Padding="12,10"
|
||||
FontSize="14"
|
||||
CaretBrush="White"
|
||||
PasswordChanged="PasswordBox_PasswordChanged"/>
|
||||
</Border>
|
||||
|
||||
<!-- Error Message -->
|
||||
<Border Background="#3d1f2f"
|
||||
CornerRadius="6"
|
||||
Padding="10,6"
|
||||
Margin="0,0,0,10"
|
||||
Visibility="{Binding HasError, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<TextBlock Text="{Binding ErrorMessage}"
|
||||
Foreground="#ff6b6b"
|
||||
FontSize="12"
|
||||
HorizontalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
<!-- Login Button -->
|
||||
<Button Command="{Binding LoginCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
Cursor="Hand"
|
||||
Margin="0,5,0,0">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="ButtonBorder" CornerRadius="8" Padding="15,12">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Color="#e94560" Offset="0"/>
|
||||
<GradientStop Color="#0f3460" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<TextBlock Text="{DynamicResource Login_LoginButton}"
|
||||
Foreground="White"
|
||||
FontSize="15"
|
||||
FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="ButtonBorder" Property="Background">
|
||||
<Setter.Value>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Color="#ff6b6b" Offset="0"/>
|
||||
<GradientStop Color="#1a5276" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="ButtonBorder" Property="Background">
|
||||
<Setter.Value>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Color="#c0392b" Offset="0"/>
|
||||
<GradientStop Color="#0a3d62" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
30
src/WPFBeginner/Views/LoginWindow.xaml.cs
Normal file
30
src/WPFBeginner/Views/LoginWindow.xaml.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using WPFBeginner.ViewModels;
|
||||
|
||||
namespace WPFBeginner.Views;
|
||||
|
||||
public partial class LoginWindow : Window
|
||||
{
|
||||
public LoginWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = App.GetService<LoginViewModel>();
|
||||
}
|
||||
|
||||
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ClickCount == 1)
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
}
|
||||
|
||||
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is LoginViewModel viewModel)
|
||||
{
|
||||
viewModel.Password = PasswordBox.Password;
|
||||
}
|
||||
}
|
||||
}
|
||||
143
src/WPFBeginner/Views/MainWindow.xaml
Normal file
143
src/WPFBeginner/Views/MainWindow.xaml
Normal file
@@ -0,0 +1,143 @@
|
||||
<Window x:Class="WPFBeginner.Views.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Title="{DynamicResource Main_Title}"
|
||||
Height="600" Width="900"
|
||||
MinWidth="700" MinHeight="400"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Background="{DynamicResource WindowBackgroundBrush}">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Top Bar -->
|
||||
<Border Grid.Row="0" Background="{DynamicResource SecondaryBrush}" Padding="15,10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Search Area -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<TextBox Text="{Binding SearchKeyword, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{DynamicResource DefaultTextBoxStyle}"
|
||||
Width="250"
|
||||
VerticalAlignment="Center"
|
||||
KeyDown="SearchTextBox_KeyDown"/>
|
||||
<Button Content="{DynamicResource Main_Search}"
|
||||
Style="{DynamicResource PrimaryButtonStyle}"
|
||||
Command="{Binding SearchCommand}"
|
||||
Margin="10,0,0,0"
|
||||
Padding="20,8"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Button Content="{DynamicResource Main_Add}"
|
||||
Style="{DynamicResource PrimaryButtonStyle}"
|
||||
Command="{Binding AddMemberCommand}"
|
||||
Margin="0,0,10,0"/>
|
||||
<Button Content="{DynamicResource Main_Delete}"
|
||||
Style="{DynamicResource SecondaryButtonStyle}"
|
||||
Command="{Binding DeleteMemberCommand}"
|
||||
Margin="0,0,10,0"/>
|
||||
<Button Content="{DynamicResource Main_Reset}"
|
||||
Style="{DynamicResource SecondaryButtonStyle}"
|
||||
Command="{Binding ResetCommand}"
|
||||
Margin="0,0,10,0"/>
|
||||
<Button x:Name="SettingsButton"
|
||||
Content="{DynamicResource Main_Settings}"
|
||||
Style="{DynamicResource SecondaryButtonStyle}"
|
||||
Click="SettingsButton_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Main Content -->
|
||||
<Grid Grid.Row="1">
|
||||
<!-- DataGrid -->
|
||||
<DataGrid ItemsSource="{Binding FilteredMembers}"
|
||||
SelectedItem="{Binding SelectedMember}"
|
||||
Style="{DynamicResource DefaultDataGridStyle}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
SelectionMode="Single"
|
||||
Margin="15">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="{DynamicResource Main_Column_Name}"
|
||||
Binding="{Binding Name}"
|
||||
Width="*"/>
|
||||
<DataGridTextColumn Header="{DynamicResource Main_Column_Call}"
|
||||
Binding="{Binding Call}"
|
||||
Width="*"/>
|
||||
<DataGridTextColumn Header="{DynamicResource Main_Column_Email}"
|
||||
Binding="{Binding EMail}"
|
||||
Width="2*"/>
|
||||
<DataGridTextColumn Header="{DynamicResource Main_Column_No}"
|
||||
Binding="{Binding No}"
|
||||
Width="*"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Settings Panel (Popup) -->
|
||||
<Popup x:Name="SettingsPopup"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=SettingsButton}"
|
||||
StaysOpen="False"
|
||||
AllowsTransparency="True">
|
||||
<Border Background="{DynamicResource SecondaryBrush}"
|
||||
BorderBrush="{DynamicResource BorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Padding="15"
|
||||
Margin="5">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="10" Opacity="0.3"/>
|
||||
</Border.Effect>
|
||||
<StackPanel Width="200">
|
||||
<!-- Language Settings -->
|
||||
<TextBlock Text="{DynamicResource Settings_Language}"
|
||||
Foreground="{DynamicResource TextBrush}"
|
||||
FontWeight="SemiBold"
|
||||
Margin="0,0,0,10"/>
|
||||
<RadioButton Content="{DynamicResource Settings_Korean}"
|
||||
IsChecked="{Binding IsKorean}"
|
||||
Style="{DynamicResource DefaultRadioButtonStyle}"
|
||||
GroupName="Language"
|
||||
Command="{Binding SetKoreanCommand}"/>
|
||||
<RadioButton Content="{DynamicResource Settings_English}"
|
||||
IsChecked="{Binding IsEnglish}"
|
||||
Style="{DynamicResource DefaultRadioButtonStyle}"
|
||||
GroupName="Language"
|
||||
Command="{Binding SetEnglishCommand}"/>
|
||||
|
||||
<Separator Margin="0,15" Background="{DynamicResource BorderBrush}"/>
|
||||
|
||||
<!-- Theme Settings -->
|
||||
<TextBlock Text="{DynamicResource Settings_Theme}"
|
||||
Foreground="{DynamicResource TextBrush}"
|
||||
FontWeight="SemiBold"
|
||||
Margin="0,0,0,10"/>
|
||||
<RadioButton Content="{DynamicResource Settings_DefaultTheme}"
|
||||
IsChecked="{Binding IsDefaultTheme}"
|
||||
Style="{DynamicResource DefaultRadioButtonStyle}"
|
||||
GroupName="Theme"
|
||||
Command="{Binding SetDefaultThemeCommand}"/>
|
||||
<RadioButton Content="{DynamicResource Settings_LightTheme}"
|
||||
IsChecked="{Binding IsLightTheme}"
|
||||
Style="{DynamicResource DefaultRadioButtonStyle}"
|
||||
GroupName="Theme"
|
||||
Command="{Binding SetLightThemeCommand}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
27
src/WPFBeginner/Views/MainWindow.xaml.cs
Normal file
27
src/WPFBeginner/Views/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using WPFBeginner.ViewModels;
|
||||
|
||||
namespace WPFBeginner.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = App.GetService<MainViewModel>();
|
||||
}
|
||||
|
||||
private void SettingsButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SettingsPopup.IsOpen = !SettingsPopup.IsOpen;
|
||||
}
|
||||
|
||||
private void SearchTextBox_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter && DataContext is MainViewModel viewModel)
|
||||
{
|
||||
viewModel.SearchCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/WPFBeginner/Views/RegistWindow.xaml
Normal file
183
src/WPFBeginner/Views/RegistWindow.xaml
Normal file
@@ -0,0 +1,183 @@
|
||||
<Window x:Class="WPFBeginner.Views.RegistWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:WPFBeginner.Controls"
|
||||
mc:Ignorable="d"
|
||||
Title="{DynamicResource Regist_Title}"
|
||||
Height="450" Width="400"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
ResizeMode="CanResize">
|
||||
|
||||
<Border Background="{DynamicResource WindowBackgroundBrush}"
|
||||
CornerRadius="8"
|
||||
BorderBrush="{DynamicResource BorderBrush}"
|
||||
BorderThickness="1">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Custom Title Bar -->
|
||||
<Grid Grid.Row="0" Background="{DynamicResource SecondaryBrush}" MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{DynamicResource Regist_Title}"
|
||||
Foreground="{DynamicResource TextBrush}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="15,10"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"/>
|
||||
|
||||
<!-- Minimize Button -->
|
||||
<Button Grid.Column="1"
|
||||
Content="─"
|
||||
Width="40" Height="35"
|
||||
Background="Transparent"
|
||||
Foreground="{DynamicResource TextBrush}"
|
||||
BorderThickness="0"
|
||||
FontSize="12"
|
||||
Command="{Binding MinimizeCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#4E4E52"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<!-- Maximize Button -->
|
||||
<Button Grid.Column="2"
|
||||
Content="☐"
|
||||
Width="40" Height="35"
|
||||
Background="Transparent"
|
||||
Foreground="{DynamicResource TextBrush}"
|
||||
BorderThickness="0"
|
||||
FontSize="12"
|
||||
Command="{Binding MaximizeCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#4E4E52"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<!-- Close Button -->
|
||||
<Button Grid.Column="3"
|
||||
Content="✕"
|
||||
Width="40" Height="35"
|
||||
Background="Transparent"
|
||||
Foreground="{DynamicResource TextBrush}"
|
||||
BorderThickness="0"
|
||||
FontSize="14"
|
||||
Command="{Binding CloseCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#E81123"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Content -->
|
||||
<StackPanel Grid.Row="1" Margin="25,20">
|
||||
<!-- Name Input -->
|
||||
<controls:InputPanel LabelText="{DynamicResource Regist_Name}"
|
||||
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
IsRequired="True"
|
||||
Margin="0,0,0,15"/>
|
||||
|
||||
<!-- Phone Input -->
|
||||
<controls:InputPanel LabelText="{DynamicResource Regist_Call}"
|
||||
Text="{Binding Call, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0,0,0,15"/>
|
||||
|
||||
<!-- Email Input -->
|
||||
<controls:InputPanel LabelText="{DynamicResource Regist_Email}"
|
||||
Text="{Binding EMail, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
IsRequired="True"
|
||||
Margin="0,0,0,15"/>
|
||||
|
||||
<!-- Employee No Input -->
|
||||
<controls:InputPanel LabelText="{DynamicResource Regist_No}"
|
||||
Text="{Binding No, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0,0,0,10"/>
|
||||
|
||||
<!-- Error Message -->
|
||||
<TextBlock Text="{Binding ErrorMessage}"
|
||||
Foreground="{DynamicResource ErrorBrush}"
|
||||
Visibility="{Binding HasError, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Buttons -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="25,0,25,20">
|
||||
<Button Content="{DynamicResource Regist_Cancel}"
|
||||
Style="{DynamicResource SecondaryButtonStyle}"
|
||||
Command="{Binding CancelCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
Width="80"
|
||||
Margin="0,0,10,0"/>
|
||||
<Button Content="{DynamicResource Regist_Register}"
|
||||
Style="{DynamicResource PrimaryButtonStyle}"
|
||||
Command="{Binding RegisterCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
Width="80"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
32
src/WPFBeginner/Views/RegistWindow.xaml.cs
Normal file
32
src/WPFBeginner/Views/RegistWindow.xaml.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using WPFBeginner.Models;
|
||||
using WPFBeginner.ViewModels;
|
||||
|
||||
namespace WPFBeginner.Views;
|
||||
|
||||
public partial class RegistWindow : Window
|
||||
{
|
||||
public event EventHandler<Member>? MemberAdded;
|
||||
|
||||
public RegistWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
var viewModel = App.GetService<RegistViewModel>();
|
||||
viewModel.MemberAdded += OnMemberAdded;
|
||||
DataContext = viewModel;
|
||||
}
|
||||
|
||||
private void OnMemberAdded(object? sender, Member member)
|
||||
{
|
||||
MemberAdded?.Invoke(this, member);
|
||||
}
|
||||
|
||||
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ClickCount == 1)
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/WPFBeginner/WPFBeginner.csproj
Normal file
23
src/WPFBeginner/WPFBeginner.csproj
Normal file
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon></ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
46
src/WPFBeginner/appsettings.json
Normal file
46
src/WPFBeginner/appsettings.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Login": {
|
||||
"User": "admin",
|
||||
"Password": "0000"
|
||||
},
|
||||
"Language": "ko-KR",
|
||||
"Theme": "DefaultTheme",
|
||||
"DefaultMembers": [
|
||||
{
|
||||
"Name": "최준영",
|
||||
"Call": "02-2141-7523",
|
||||
"EMail": "b17314.tdc.hanmacgroup@gmail.com",
|
||||
"No": "b17314"
|
||||
},
|
||||
{
|
||||
"Name": "엄지숙",
|
||||
"Call": "-",
|
||||
"EMail": "b23072@hanmaceng.co.kr",
|
||||
"No": "b23072"
|
||||
},
|
||||
{
|
||||
"Name": "정나래",
|
||||
"Call": "-",
|
||||
"EMail": "b23009@hanmaceng.co.kr",
|
||||
"No": "b23009"
|
||||
},
|
||||
{
|
||||
"Name": "강근아",
|
||||
"Call": "02-2141-7317",
|
||||
"EMail": "m21318.tdc.hanmacgroup@gmail.com",
|
||||
"No": "m21318"
|
||||
},
|
||||
{
|
||||
"Name": "김근형",
|
||||
"Call": "02-2141-7508",
|
||||
"EMail": "b20311.tdc.hanmacgroup@gmail.com",
|
||||
"No": "b20311"
|
||||
},
|
||||
{
|
||||
"Name": "정호진",
|
||||
"Call": "-",
|
||||
"EMail": "b23022@hanmaceng.co.kr",
|
||||
"No": "b23022"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user