[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:
최준영
2025-12-29 10:03:49 +09:00
parent ef2a6522d1
commit 09768ad928
28 changed files with 2186 additions and 0 deletions

60
CLAUDE.md Normal file
View 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
View 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
View 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
View 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>

View 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>();
}
}

View 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)
)]

View 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>

View 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();
}
}

View 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;
}
}

View 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";
}

View 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);
}
}

View 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>

View 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>

View 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>

View 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>

View 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));
}
}

View 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);
}
}

View 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();
}
}

View 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));
}
}

View 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();
}
}

View 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>

View 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;
}
}
}

View 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>

View 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);
}
}
}

View 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>

View 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();
}
}
}

View 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>

View 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"
}
]
}