기록
article thumbnail
728x90

배경

WPF를 이용하여 DB 연동과 데이터 바인딩한 ListView 컨트롤을 좀 더 사용하고 싶어 졌습니다. 어떤 주제로 만들면 재미있을까 고민하다가 지금 Tistory의 콘텐츠 카테고리 이름을 오름차순으로 정렬해 주고 상태를 관리하는 용도의 프로그램을 만들면 좋겠다는 생각이 들었습니다. 

과거에 C 프로그래밍을 공부하면서 퀵 정렬을 사용하여 만든 적이 있었는데 이번에는 진짜 사용하기 수월한 프로그램을 만들고 싶어 졌습니다.

 

2023.05.09 - [개발/Computer Science] - [C프로그래밍] 나의 Tistory 카테고리 알파벳 순서로 정렬하기

 

[C프로그래밍] 나의 Tistory 카테고리 알파벳 순서로 정렬하기

시작 C프로그래밍을 수강하는 과목이 있어서 공부할 겸 저의 티스토리 카테고리 알파벳 순서대로 정렬하는 실행 파일을 만들고 싶어 졌습니다. 책이랑 강의만 듣는 게 조금 지루해서 인터넷 검

hyangforest.tistory.com

목표

  • ListView 컨트롤 사용하여 DB 연동하기 
  • DB 연동은 Entity Framework Core 환경과 Stored Procedure 사용하기
  • 저장/수정/삭제 시 정렬에 관한 데이터 체크 및 정리하여 조작하기

개발 화면 및 화면 전환 방법

습관 카테고리 관리 화면

개발 카테고리 관리 화면

저는 User Control과 ContentPresenter 컨트롤을 사용하여 화면 전환을 하였습니다. Main 윈도우 창에서 ContentPresenter 컨트롤을 생성하여 필요에 따라 "습관 카테고리 관리" User Control과 "개발 카테고리 관리" User Control로 Content를 바꿔주었습니다.

public MainWindow()
{
    InitializeComponent();

    

    // 화면
    _habitCategoryView = new HabitCategoryView();
    _improvementCategoryView = new ImprovementCategoryView();

    ContentPresenter.Content = _habitCategoryView;
}

private void btnImprovement_Click(object sender, RoutedEventArgs e)
{
    ContentPresenter.Content = _improvementCategoryView; 
}

private void btnHabit_Click(object sender, RoutedEventArgs e)
{
    ContentPresenter.Content = _habitCategoryView; 
}

ListView 컨트롤에 EF & 저장 프로시저 연동하기

User Control의 Load 이벤트에서 ListView ItemSource를 EF & SP로 DB의 데이터를 가져와서 바인딩시켜 주었습니다. Load 이벤트를 만들지 않고 User Control에 초기화 메서드 이후에 해도 되는데, 이벤트 확실하게 의미를 살려주고 싶어서 

Load 이벤트에 ListView 데이터바인딩 코드를 넣었습니다.

 

저장 프로시저로 연동하고 싶었던 이유는 Model을 그대로 사용하는 방법으로 해도 되지만 데이터 가공해서 보여주고 싶어서 저장 프로시저용 Model 클래스를 따로 만들어서 사용했습니다.

 

dotnet ef tool을 사용하면 migrations, database update 구문을 사용할 때 DB에 Table이 생성될 수 있으니 생기지 않도록 코드를 잘 확인해야 합니다. 

코드

User Control의 XAML

<ListView Name="listView" Grid.Column="0" SelectionChanged="listView_SelectionChanged">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="ID" Width="30" DisplayMemberBinding="{Binding Id}" />
            <GridViewColumn Header="카테고리명" Width="Auto" DisplayMemberBinding="{Binding KORCategoryName}" />
            <GridViewColumn Header="사용여부" Width="70" DisplayMemberBinding="{Binding Usage}" />
            <GridViewColumn Header="공개여부" Width="70" DisplayMemberBinding="{Binding Open}" />
            <GridViewColumn Header="정렬순서" Width="60" DisplayMemberBinding="{Binding SortOrder}" />
            <GridViewColumn Header="등록일시" Width="150" DisplayMemberBinding="{Binding RegistrationDate}" />
            <GridViewColumn Header="수정일시"  Width="150" DisplayMemberBinding="{Binding ModificationDate}" />
        </GridView>
    </ListView.View>
</ListView>

User Control 클래스

private CollectionViewSource? collectionViewSource { get; set; }
public ICollectionView? collection
{
    get {

        if (collectionViewSource != null)
        {
            return collectionViewSource.View;
        }
        else 
        {
            return null;
        }
    }
}

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    if (_context != null)
    {
        collectionViewSource = new CollectionViewSource();
        collectionViewSource.Source = Find.FindStoredProcedureNameFromSql(_context, "sp_Get_HabitCategories");
        listView.ItemsSource = collection;

        // 정렬 순서 최대값 + 1
        this.txtSortOrder.Text = this.GetMaxSortOrder();
    }
}

DB Context 클래스(DbContext 클래스를 상속) 

public DbSet<spHabitCategory> sp_HabitCategories { get; set; }

DB 클래스

public static List<spImprovementCategory>? FindStoredProcedureFromSql(AppDbContext context)
{
    return context?.sp_ImprovementCategories?.FromSql($"EXEC sp_Get_ImprovementCategories").ToList();
}

sp_Get_HabitCategories 

SELECT Id
	 , KORCategoryName
	 , Description
	 , SortOrder
	 , UsageStatus
	 , IIF(UsageStatus = 'Y', '사용', '미사용') as Usage
	 , OpenStatus
	 , IIF(OpenStatus = 'Y', '공개', '비공개') as [Open]
	 , FORMAT(RegistrationDate, 'yyyy-MM-dd HH:mm:ss') as RegistrationDate
	 , FORMAT(ModificationDate, 'yyyy-MM-dd HH:mm:ss') as ModificationDate
 FROM dbo.HabitCategories
 ORDER BY KORCategoryName, SortOrder

미해결

ListView의 Load 이벤트에서 데이터 바인딩하고 [저장], [삭제] 버튼 이벤트를 이용하여 데이터를 조작하고 변경된 DB 내용을 다시 ListView의 ItemSource에 데이터 바인딩을 해도 Update 되지 않는다는 점이었습니다. UI가 변경된다는 알림을 인터페이스를 통해 알려주고 하는 내용이 있었는데, 몇 번 테스트를 해도 잘 되지 않았습니다. context를 새로 생성해서 바인딩하면 되긴 하지만, 다시 데이터 조작하면 그때는? 일관된 context 상태를 유지하지 못하겠다 싶었습니다. 그래서 생각한 방법은 Main Window에서 ContentPresenter 컨트롤의 Content를 바꿔주는 것처럼 User Control에서 Main Window의 ContentPresenter 컨트롤에 접근해서 내용을 다시 한번 새로 바꿔주자는 방법을 생각했습니다.

 

DependencyObject parent = VisualTreeHelper.GetParent(this);

while (!(parent is MainWindow))
{
    parent = VisualTreeHelper.GetParent(parent);
}

if (parent is MainWindow mainWindow)
{
    mainWindow.ContentPresenter.Content = new HabitCategoryView();
}

  

다음에 다른 디자인 패턴을 사용해서, 좀 더 좋은 방법을 찾아봐야겠습니다.

정렬순서 데이터 관리

DB에 이미 저장되어 있는 정렬순서로 새로 등록하거나 수정할 때 기능을 하지 못하도록 안내하는 것보다 이미 등록된 순서가 있는데 이 순서대로 다시 정렬하겠습니까? 메시지 알림 박스를 보이도록 하였습니다. [확인]을 눌렀을 때 데이터를 정리하고 저장/수정/삭제하는 로직은 DB 저장 프로시저를 사용하였습니다. 

저장

삽입하기 전에 텍스트 박스에 입력한 정렬순서보다 큰 값을 가지고 있는 데이터들의 정렬순서를 1씩 증가하였습니다. 이후 데이터를 INSERT 하였습니다.

UPDATE dbo.HabitCategories
   SET SortOrder = SortOrder + 1
	 , ModificationDate = GETDATE()
 WHERE SortOrder >= @SortOrder


INSERT INTO dbo.HabitCategories
(
	KORCategoryName
,	[Description]
,	SortOrder
,	UsageStatus
,	OpenStatus
,	RegistrationDate
)
VALUES
(
	@KORCategoryName
,	@Description
,	@SortOrder
,	@UsageStatus
,	@OpenStatus
,	GETDATE()
)

수정

저장은 까다롭지 않았는데, 수정이 까다로웠습니다. 수정은 조건을 2가지로 분리하였습니다. 

  1. 수정하고 싶은 정렬순서가 기존의 정렬순서보다 적은 값일 때
    수정하고 싶은 정렬순서 : 1
    기존 정렬순서 : 7

    이 경우에는 2, 3, 4, 5, 6번의 정렬 순서들이 1씩 증가해야 합니다.

  2. 수정하고 싶은 정렬순서가 기존의 정렬순서보다 큰 값일 때
    수정하고 싶은 정렬순서 : 3
    기존 정렬순서 : 1

    이 경우에는 2, 3번의 정렬순서들이 1씩 감소해야 합니다.
DECLARE @SORT int -- 기존 정렬순서
DECLARE @TEMP_ID int
DECLARE @LOOP int 

-- 수정되는 Item Id의 SortOrder 
SELECT @SORT = SortOrder
  FROM dbo.HabitCategories
 WHERE Id = @Id

-- 수정하고 싶은 정렬순서의 Item의 Id를 임시로 저장한다
SELECT @TEMP_ID = Id
  FROM dbo.HabitCategories
 WHERE SortOrder = @SortOrder 
 
 -- 기존보다 수정할 정렬순서가 작을 때
 IF (@SORT > @SortOrder)
 BEGIN
   -- 루프 기준
   -- 입력 값과 기존 값의 중간 정렬순서
   SET @LOOP = @SORT - 1
      
   -- 중간 정렬 값들 큰값에서 -1 하며 정렬하기
   WHILE (@SortOrder < @LOOP)
   BEGIN

	  UPDATE dbo.HabitCategories
	     SET SortOrder = SortOrder + 1
		   , ModificationDate = GETDATE()
	   WHERE SortOrder = @LOOP

	     SET @LOOP = @LOOP - 1
		 
   END

   -- 원래 정렬순서에서 한칸 밀기
   UPDATE dbo.HabitCategories
	   SET SortOrder = SortOrder + 1
		 , ModificationDate = GETDATE()
	 WHERE Id = @TEMP_ID
  
  -- 입력 순서 자리 꿰차기
   UPDATE dbo.HabitCategories
	   SET SortOrder = @SortOrder
		 , ModificationDate = GETDATE()
	 WHERE Id = @Id
	
 END
 ELSE IF (@SORT < @SortOrder) -- 기존보다 수정할 정렬순서가 클 때
 BEGIN
		
	SET @LOOP = @SORT + 1

	WHILE (@LOOP <= @SortOrder)
	BEGIN
		
	  UPDATE dbo.HabitCategories
	     SET SortOrder = SortOrder - 1
		   , ModificationDate = GETDATE()
	   WHERE SortOrder = @LOOP

	     SET @LOOP = @LOOP + 1

	END

  -- 입력 순서 자리 꿰차기
   UPDATE dbo.HabitCategories
	   SET SortOrder = @SortOrder
		 , ModificationDate = GETDATE()
	 WHERE Id = @Id
	
 END

 UPDATE dbo.HabitCategories
	SET KORCategoryName = @KORCategoryName
	  , [Description] = @Description
	  , UsageStatus = @UsageStatus
	  , OpenStatus = @OpenStatus
	  , ModificationDate = GETDATE()
  WHERE Id = @Id

삭제

삭제할 정렬순서부터 최대 정렬순서까지 LOOP로 정렬순서를 1씩 감소시켜 정렬순서를 UPDATE 합니다.

DECLARE @TEMP_SORT	int
DECLARE @MAX_SORT	int
DECLARE @LOOP		int

SELECT @TEMP_SORT = SortOrder 
  FROM dbo.HabitCategories
 WHERE Id = @Id

SELECT @MAX_SORT = ISNULL(MAX(SortOrder), 0) 
  FROM dbo.HabitCategories

   SET @LOOP = @TEMP_SORT + 1

 WHILE (@LOOP <= @MAX_SORT)
 BEGIN

	UPDATE dbo.HabitCategories
	   SET SortOrder = SortOrder - 1
	     , ModificationDate = GETDATE()
	 WHERE SortOrder = @LOOP

	   SET @LOOP = @LOOP + 1

 END

DELETE
  FROM dbo.HabitCategories
 WHERE Id = @Id

코드에서 정렬할까, DB에서 정렬할까 고민했었는데 EF와 SP 연동에 초점을 두고 있어서 DB로 구현했었는데 다음에는 C# 코드로 정렬 로직을 만들면 더 재밌겠다 싶었습니다. 그러면 DB 호출 코드도 좀 달라질 것 같은데, 새로운 WPF 코드 컨트롤 접근하는 실습을 할 수 있을 것 같다는 생각이 드네요. 다음에는 코드로 정리하는 법을 해봐야겠습니다.

GitHub

깃허브에 프로젝트 기능이 생긴 걸 예전에 확인했는데 이번에 한번 사용해 봤습니다. 간단한 기능만 사용해서 유용하게 사용하진 못했지만, 다음 프로젝트에서는 초기부터 잘 설정해서 사용하면 풍부하게 프로젝트 이슈 관리를 할 수 있겠다는 자신이 생겼습니다.

 

https://github.com/hyangforest/TistoryCategoryManager

 

GitHub - hyangforest/TistoryCategoryManager: 개인 Tistory 블로그 콘텐츠 카테고리 관리 정렬용 프로그램입니

개인 Tistory 블로그 콘텐츠 카테고리 관리 정렬용 프로그램입니다. Contribute to hyangforest/TistoryCategoryManager development by creating an account on GitHub.

github.com

 

728x90