[C#]WinForm TreeView CheckBoxes 기능 완벽 사용하기

개요

윈폼 트리뷰는 각 노드에 체크박스를 사용할 수 있는 속성이 있습니다. 하지만 각각의 노드를 체크할 수 있지, 부모 노드를 체크하면 자식 노드도 같이 모두 선택되거나(또는 반대거나) 자식 노드를 모두 체크하면 부모 노드도 체크되는 등의 UX는 직접 구현해야 합니다. 이번 포스팅에서는 해당 부분을 구현하는 코드를 안내합니다.


TreeView의 체크 기능은 반쪽이다.

저는 지금 생산설비의 각 상황과 그 추세, 작업량등을 파악하는 종합 모니터링 프로그램을 만들고 있습니다. 물론 자세한건 설명드리긴 그렇고, 전체적인 UI는 왼쪽 베너에서 A라는 항목을 선택하면 오른쪽 빈 공간에 그와 관련된 여러가지 차트를 그려주는 프로그램이라고 생각하면 쉽습니다.

어느정도 사용이 가능한 상태로 만들었더니 UX측면에서의 요구사항이 들어왔는데, 바로 특정 항목을 선택하면 해당 항목들만 순환하여 보여달라는 것이였죠. 처음부터 베너 구조가 트리뷰로 되어 있었기 때문에 체크표시를 구현하는건 트리뷰의 속성 하나(CheckBoxes)를 true로 설정해주면 되기 때문에 문제가 없었죠.


근데, 개요와 마찬가지로, 이는 어디까지나 트리뷰의 각 노드별로 체크만 만들어주지 일반적으로 아는 부모노드, 자식노드간의 체크여부와 그에대한 동작은 전혀 구현되지 않아 있습니다. WPF는 어떨지 모르겠는데, 차트를 그려야 하니 그냥 다루기 쉬운 WinForm으로 만들고 있었으니 엎지는 못하고, 그냥 이대로 작업해야 해서 일단 만들었습니다. 만들면서 인터넷에 찾아보니 완벽하게 정리된 방법은 없는것 같아 포스팅 해 봅니다.


구현 방법

두가지 경우를 생각해야 합니다.


부모노드를 체크하거나 체크를 풀 때

자, 우선 부모노드의 체크상태에 따라 그에 해당하는 자식노드의 체크상태를 바꿔줘야 합니다. 이는 어렵지 않아요. 간단하게 생각해보면.

1. 현재 체크상태가 바뀐 노드를 가져온다.
2. 해당 노드의 자식 노드를 가져온다.
3. 자식 노드들을 현재 부모노드의 체크상태에 맞춰준다.

라고 할 수 있습니다.


자식노드를 체크하거나 체크를 풀 때

이때도 간단하게 한번 상상해 봅시다. 어떻게 해야 할지.

1. 현재 선택된 노드의 부모 노드를 가져온다.
2. 해당 노드의 자식 노드들의 체크상태를 확인한다.
3. 자식 노드들이 모두 true이면 부모 노드는 true, 하나라도 false라면 부모 노드는 false로 맞춰준다.

이정도만 하면 흔히 아는 트리뷰의 UX를 구현할 수 있습니다.


이 두가지 경우를 한번에 처리해야 한다

위의 두가지 경우를 확인하면 이제 이벤트에 어떻게 등록 할 지를 찾아야 합니다. 단순히 해당 노드가 부모노드일때, 자식노드일때만을 구분해서 처리한다면 레벨이 3 이상일 경우에 레벨 2의 노드의 체크값이 올바르게 처리되지 않게 될테니까요.

사실 이 UX를 구현하는데 있어서 이부분을 처리하는것이 가장 중요한데, Check를 bool 타입으로 한다면 그렇게 어렵지 않습니다. bool? 타입일 경우에는 조금 어려울 것이고 제가 만드는 프로그램은 레벨 2 까지만 표시되도록 했기 때문에 굳이 이부분을 구현할 필요는 없었는데, 완벽하게 구현하기 위해서는 이정도 처리까지는 해야할 테니 대충 bool 타입으로 표시하는 방법으로 하도록 하죠.


코드 구현

선택한 노드의 자식노드들을 처리

우선 처리하는 코드부터 먼저 작성해봅니다. 첫번째로 현재 체크상태를 변경한 노드의 자식노드를 모두 해당 노드의 체크상태로 바꿔줘야 합니다. 신경써야 할 부분은 자식노드는 또다른 자식노드를 가지고 있을 수 있기 때문에 해당 노드가 또다른 자식노드를 가지고 있는지 확인하고 그 노드들도 같은 값으로 바꿔줘야 겠죠.

private void ChildNodeChecking(TreeNode selectNode)
{
    foreach (TreeNode tn in selectNode.Nodes)
    {
        tn.Checked = selectNode.Checked;
        ChildNodeChecking(tn);
    }
    return;
}

현재 선택된 노드를 가져와서 그의 자식 노드들에게 선택된 노드의 상태를 적용시켜 줍니다. 그리고 재귀함수를 통해서 해당 노드를 부모노드라고 가정하고 다시 해당하는 자식 노드들에게 그의 부모노드의 체크값을 적용시켜 줍니다. 그리고 다시 재귀함수를…

이런식으로 재귀함수를 통해 반복하면서 모든 자식노드들에게 체크값을 적용시켜 줍니다.


선택한 노드의 부모노드를 처리

부모노드를 처리하려면 먼저 자식노드들의 체크상태가 어느 하나라도 false라면 부모노드도 false로 처리하면 됩니다. 굳이 자식노드 전체를 볼 필요는 없고 직속(?) 자식노드들만 확인하면 되겠죠. 단, 해당 노드의 부모노드가 또다른 노드의 자식노드라고 한다면 이를 반복해서 처리해야 할 겁니다. 앞의 경우는 노드의 하뒤로 내려간다면 이 경우는 노드의 상위로 올라가면서 처리하는 식이죠.

public void ParentNodeChecking(TreeNode selectNode)
{
    TreeNode t = selectNode.Parent;
    if (t != null)
    {
        t.Checked = true;
        foreach (TreeNode tn in t.Nodes)
        {
            if (!tn.Checked)
            {
                t.Checked = false;
                break;
            }
        }
        ParentNodeChecking(t);
    }
}

현재 선택된 노드의 부모노드를 확인하고 있다면 먼저 해당 노드를 체크한 다음, 해당 노드의 자식노드중 어느 하나라도 false라면 해당 노드를 false로 처리, 그리고 다시 해당 노드의 부모노드를 확인하고…

역시 마찬가지로 재귀함수를 통해 반복하도록 하면서 자식노드들을 검사하고 체크값을 결정해 주도록 하는거죠.


이벤트로 처리되도록

이렇게 코드로 구현을 했으니, 체크를 빼든 하던 해당 동작이 발생하면 이 코드들을 작동시킬 수 있도록 이벤트에 달아야 합니다. TreeView의 AfterCheck 라는 이벤트가 있는데 여기에 등록해줘야 합니다. 근데 단순히 함수를 붙혀주는것으로 끝낸다면 프로그램은 스텍오버플로우가 되면서 뻗어버릴겁니다.

이유는 위의 작업을 통해 트리뷰의 체크값을 바꿀경우 또다시 해당 이벤트가 발생될 것이고 그게 무한반복 될 테니까요. 그러니까 정확히 해당 작업들을 하기 전에 먼저 이벤트 함수를 없애주고, 작업이 끝나게 되면 다시 이벤트를 붙혀 주는 것이 중요합니다.

private void treeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
    treeView1.AfterCheck -= treeView1_AfterCheck;
    ChildNodeChecking(e.Node);
    ParentNodeChecking(e.Node);
    treeView1.AfterCheck += treeView1_AfterCheck;
}

이런식으로 말이죠.


확인

잘 작동되는지 제가 작업중인 프로그램에 테스트 노드를 붙혀서 확인해 보았습니다. 잘 되네요.


bool? 타입으로 구현할 경우에는 ParentNodeChecking 함수에서 자식노드의 값을 확인하는 부분을 단순히 false 하나 만나면 끝내는 것이 아니라 각각 노드의 상태를 확인하여 모든게 false라면 false로 모든게 true라면 true로 하나 이상의 false가 나온다면 ? 로 처리해서 부모노드에 적용하면 될 것 같은데, 트리뷰의 체크박스가 bool? 타입을 지원하는지는 잘 모르겠습니다. 여튼 그건 직접 해보시는것 추천.

Minny_

,

기본 사항은 크게 바뀌지 않았습니다. 


2017/08/11 - [Tip] - MsSQLtoMySQL 1.0 : MSSQL의 테이블을 MySQL로 옮기는 프로그램



다만 필요에 의해 NULL값일 경우 무시할 수 있도록 처리하였는데, 아주 단순하게 수정한 것이라 저희 테스트 환경에서는 정상 동작하도록만 하였습니다. 방식은 데이터를 읽어들일때 안에 값이 "", 그러니까 string.Empty 일 경우 해당 칼럼을 무시하도록 설정해 뒀기 때문에 


1. MsSQL 원본 데이터 값이 의도적으로 "" 으로 처리되었을 경우에는 MySQL 사본 데이터에 NULL이 됩니다.

2. 만약 해당 칼럼이 NULL값을 비허용 할 경우 MySQL 사본 데이터가 들어가지 않게 됩니다.



물론 해결 방법은 있겠지만, 현재 진행중인 프로그램은 현장의 실제 데이터베이스에 원하는 값대로 설정되어 들어가므로 더이상의 유지보수는 제가 정말 심심하면 하도록 하죠.


mssqltomysql_1.01.zip


Minny_

,

회사에서 필요함에 따라 만든 프로그램입니다. 따라서 회사 로고가 들어가 있습니다.


MsSQL 데이터베이스의 특정 테이블을 그대로 MySQL 데이터베이스의 같은 이름과 형태의 테이블을 그대로 넣어주고, MySQL로 들어간 데이터들은 MsSQL 데이터베이스에 있는 데이터는 지워주는 프로그램입니다.


샘플 프로그램으로 만들어서 버그가 있을 순 있는데, 일단 로컬 테스트는 정상적으로 수행되었으니 필요하시면 쓰셔도 될 것 같습니다. 단 몇가지 제한사항이 있는데, 


1. MSSQL 원본 데이터에 NULL 값이 들어가 있으면 안되며(NULL 값을 빈 문자열로 읽어버려 MSSQL에서 데이터 삭제가 되지 않고 -> 다음 타임에 MySQL에는 중복된 데이터가 쓰이면서 더이상 작동되지 않을 것 입니다.

2. MSSQL의 테이블에 특정 칼럼이 TEXT계열로 되어 있으면 안되며(MSSQL에서 데이터 삭제시 TEXT는 = 연산자로 비교할 수 없으며 그로 인해 데이터 삭제가 되지 않고 -> 다음 타임에 MySQL에는 중복된 데이터가 쓰이면서 더이상 작동되지 않을 것 입니다.

3. MSSQL 원본 테이블과 MySQL의 사본 테이블 형태는 같아야 합니다.(칼럼 이름을 직접 비교하기 때문입니다)

4. 원본 테이블에는 중복된 데이터가 존재할 경우 MySQL에는 첫 데이터는 정상적으로 들어가나 두번째 데이터는 중복으로 들어가면서 작동되지 않을 것 입니다. 이는 1,2 번에서 발생하는 문제 원인과 동일합니다.

5. 추가사항으로 DB의 페스워드가 노출됩니다. 관리자만 사용하세요.


현재는 이 프로그램에서 테이블을 나눠 임의의 테이블로 옮기는 작업이 필요하여 해당 프로그램의 문제점을 수정하지 않고 작업중이므로 버그 수정 및 문제점 개선은 하지 않습니다. 개선은 충분히 할 수 있을 것 같습니다만.... 그건 제가 정망정말정말정말 할 것 없을때 하죠 뭐


다운로드는 아래에서...

mssqltomysql_1_0.zip

Minny_

,

[C#] 자동 시작하는 프로그램

이것저것 응용해서

프로그래밍을 하는 분들은 이정도의 기능을 만드는것은 기본중에 기본이라고 생각한다. 그래서 나 같은 사람은 인터넷 검색으로 만드는 경우가 대부분이다보니 이것저것 응용해서 만드는 방법을 직접 해보려고 생각했고, 그래서 내가 만드는 프로그램들 중 요구사항이 '자동 시작해야 한다.' 라고 한다면 이 글에서 얘기할 방법으로 만든다. 어려운 방법은 아니다. 생각해보면 간단한게, 1. 먼저 Windows가 부팅되면 프로그램이 자동으로 켜지도록 할 수 있게 해야하고, 2. 동작이 필요한 경우 이전 상태에 따라서 자동으로 해당 동작이 되도록 해야 한다. 3. 혹시나 모를 상황에 대비해 동작을 중지할 경우 Windows 부팅시 프로그램이 자동으로 켜지도록 하는 부분을 없애 줄 수 도 있어야 한다. 정도...

이를 위해서 필요한 것은 레지스트리를 다룰 수 있어야 하고, 현재 상태를 저장할 수 있어야 한다. 자동으로 켜지도록 하는 방법은 대표적으로 두가지가 있는데 시작 프로그램에 프로그램 바로가기를 넣는 방법과, 레지스트리에 시작 프로그램을 등록하는 방법이 있다. 나는 레지스트리를 건드는 방법을 아주아주 싫어하기 때문에 첫번째 방법을 쓰고 싶었지만, 쉽지 않으니 그냥 간단하게 레지스트리를 사용하는 방법을 쓴다. 그리고 현재 상태를 저장하는 방법도 몇가지가 있는데, 제일 간단한 프로그램 내부 프로퍼티에 저장하는 방법을 사용한다.


시작 프로그램 등록을 위한 레지스트리 등록

Windows가 부팅되면 시스템 권한 레지스트리의 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run' 경로에 등록되어 있는 값들을 확인하고 이 값들이 가리키는 프로그램들을 실행시킨다. 그리고 나서 유저 권한 레지스트리의 같은 경로에서 같은 동작을 한다. 시스템 권한 레지스트리는 관리자 권한이 필요하기 때문에 생략, 우리는 유저 권한 레지스트리에 시작 프로그램으로 등록한다. 나는 이 코드를 메소드로 만들어 필요할때마다 쓴다.

    private void registrySet()
    {
        var rkApp = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
        rkApp.SetValue("name", System.IO.Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
    }


코드에서 안에 name에는 상황에 맞춰 적당히 수정해주면 된다. 보통은 프로그램 이름이 들어갈테니 그냥 네임스페이스 값을 바로 넣어도 된다. 물론 나는 해보지 않았으니 확인 필요. 프로그램 경로와 파일 이름까지 정확히 써줘야 하는데, 디버그와 릴리즈 프로그램간 차이와 WPF프로그램을 만들다보니 통상적인 프로그램의 경로를 파악하는 코드의 결과값이 간혹 다르게 나왔다. 그래서 아주 확실하게 현제 프로세스의 매인모듈이 있는 프로그램 파일의 이름과 경로를 가져오도록 System.IO.Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName 를 썼다. 혹시나 마음에 들지 않는다면 통상적인 방법으로 써도 괜찮을 것 같지만, 나는 이미 그 방법의 신뢰를 잃어버렸으니 이렇게 쓴다.


자동 동작을 위한 코드 수정

시작 프로그램이 띄워지기만 하고 동작을 하지 않으면 소용 없다. 동작도 되어야 하는데, 이럴때 설정값이 저장되고 불러오는 기능이 필요하다. XML로 저장하거나, TXT로 저장하거나, ini로 저장하거나 방법은 많지만 굳이 자동 동작 하나를 위해 파일을 읽고 쓰는 기능을 만들 필요는 없다고 생각한다. 그래서 프로퍼티의 설정값을 추가해서 그값을 이용하기로 한다. 나는 보통 autoStart라는 bool 변수로 저장하고 기본값은 False로 한다. 그리고 동작이 시작하는 부분에 해당 값을 true로 만들고 저장. 마찬가지로 동작이 종료되는 부분에 해당 값을 false로 만들고 저장. 이렇게 하면 이전에 프로그램이 종료되었을때 어떻게 종료되었는지를 확인할 수 있다.

설정값만 저장하면 의미가 없다. autoStart가 true가 될 때 시작프로그램을 등록해주는 레지스트리를 써주면 이제는 동작을 시작한 다음 PC를 껐거나 예기치 못한 상황으로 PC가 꺼졌을때 다음에 다시 켜면 프로그램이 띄워진다. 여기다가 프로그램이 열렸을때 autoStart가 true라면 해당 동작 이벤트가 자동으로 시작되도록만 해주면, 자동 동작 부분은 끝. 아래 코드는 간단하게 버튼이 눌러지면 레지스트리를 등록하고, autoStart를 true로 변경해주고 설정값을 저장하도록 되어 있다. 그리고 프로그램이 시작되고 Form_Load 이벤트가 불려지면 autoStart 값을 확인하고 동작 이벤트를 발생시키도록 한다.


  • 동작 이벤트

    connect = false;
    private void btnConnect_Click(object sender, EventArgs e)
    {
        if(!connect){
            ...
            connect = true;
    
            registrySet();
            Properties.Settings.Default.autoStart = true;
            Properties.Settings.Default.Save();
        }
        else{
        ...
        }
    }
    
  • 시작 이벤트

    private void Form1_Load(object sender, EventArgs e)
    {
        if (Properties.Settings.Default.autoStart)
        {
            btnConnect_Click(null, null);
        }
    }
    


시작 프로그램 등록 해지를 위한 레지스트리 삭제와 자동 동작 해제

이렇게까지 만들면, 동작 시작 후 PC를 껐다 다시 켜면 자동으로 시작하는 모습을 볼 수 있다. 문제는 동작을 종료했을때도 다음에 PC가 켜질때 자동으로 켜지고 동작도 시작될 것이다. 즉, 자동 동작도 해제해 줘야 하고 자동 시작도 해제해 줘야 한다. 생각해보면 당연하겠지만, 이건 위의 코드에서 동작 이벤트 안에 else부분에 들어가면 되는데 autoStart를 false로 바꿔주고 저장한 다음, 마찬가지로 아까 등록했던 레지스트리를 삭제해 주는 코드를 만들어 동작시키면 된다. 레지스트리의 이름은 위에 레지스트리 등록했을때의 이름으로 맞춰줘야 한다. 또한 추가로 레지스트리 값 이름은 통상적으로 쓰는 이름보다는 프로그램이 구분될 수 있도록 하자. 잘못하다간 엄한 프로그램을 건들 수 도 있다.


  • 레지스트리 삭제

    private void registryDel()
    {
        var rkApp = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
        rkApp.DeleteValue("name");
    }
    
  • 자동 시작 해제까지 포함한 동작 이벤트

    connect = false;
    private void btnConnect_Click(object sender, EventArgs e)
    {
        if(!connect){
            ...
            connect = true;
    
            registrySet();
            Properties.Settings.Default.autoStart = true;
            Properties.Settings.Default.Save();
        }
        else{
        ...
        connect = false;
    
        registryDel();
        Properties.Settings.Default.autoStart = false;
        Properties.Settings.Default.Save(); 
        }
    }
    


Minny_

,

[C#] WPF에서 WinForm의 Chart 사용하기


WPF에는 차트가 없어?

내가 못 찾는건지 아니면 진짜 없는건지는 모르겠는데, WPF 프로그램을 개발하던 중 Chart를 넣어야 하는데 없어서 상당히 곤란했다. WinForm의 Chart는 학교에서 데이터베이스 연동 실습을 진행할때 많이 사용했었고, 그 기능이 상당히 많아 후에 WinFrom 프로그램을 만들때도 잘 사용했던 기억이 있는데, 없었다. 일단 WPF로 짜고 있었던 프로그램이라 어떻게든 이걸 해결해야 하니 혹시 무료 컴포넌트가 없나 찾아봐도 무료는 없고 유료는 엄청나게 기능이 좋았다. 물론 개발하는데 유료는 내가 쓰는데도 어려움이 있으니 어떻게 할까 고민하면서 인터넷을 뒤지다보니 WPF안에 WinForm 컴포넌트를 넣는 방법이 있었다. WinForm의 Chart는 내가 많이 써봤고, 이렇게 해결될 수 있다면 굳이 어렵고 돈 들이는 방법을 쓸 필요는 없다. 단 내 코딩 실력이 형편없으니 코드가 어지러워지는건 어쩔 수 없겠지만...


WPF안에 WinFrom을 넣는 방법

https://msdn.microsoft.com/ko-kr/library/ms742875(v=vs.110).aspx

MSDN문서이고 설명도 아주 잘 되어 있다. 이를 사용하면 간단한 WinForm 컨트롤은 쉽게 WPF안에서 보여질 수 있다. 그렇게 나는 Chart를 넣을 수 있겠거니, 의외로 쉽게 풀리겠군. 싶어서 만들었는데, 계속 알 수 없는 문제가 발생했다. 지금은 기억 안나는데, 위의 MSDN문서처럼 적당히 처리하고, 안에 <wf.MaskedTextBox... 라고 되어 있는 부분을 <wf.Chart... 로 넣었으나 컴파일러가 사용할 수 없다는 늬양스의 메시지를 던진다. 알아보니 Chart 컨트롤은 System.Windows.Forms에 속해있지 않고, System.Windows.Forms.DataVisualization.Charting안에 속해있기 때문에 Chart 컨트롤을 사용할 수 없는것. 그래서 이를 응용해서 네임스페이스 매핑을 조금 수정해보았다. xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" 대신에 xmlns:wf="clr-namespace:System.Windows.Forms.DataVisualization.Charting;assembly=System.Windows.Forms.DataVisualization.Charting" 이렇게, 그렇게 하면 네임스페이스와 어셈블리를 찾을 수 없다는 에러를 띄운다. 아무래도 이렇게 대응이 가능한 경우는 WinFrom 자체가 포함하고 있는 컨트롤 정도인가 보다.


WPF안에 WinForm을 넣는 다른 방법

즉 위와 같은 방법은 통하지 않는다. 하지만 포기할 이유는 없는게, WinForm이 WPF안에 들어가는건 확인했으니, 이를 응용하거나 다른 방법으로 충분히 넣을 수 있다고 생각했다. using이 어떻든간에 Chart 컨트롤은 WinForm 컨트롤이니까. 우선 WindowsFormsHost가 어떻게 돌아가는지 확인이 필요했다. 여기저기 문서를 확인해보니, WindowsFromsHost는 일종의 컨테이너고, WPF의 일반적인 컨테이너와는 다르게 WinForm을 담아두고 보여줄 수 있는 컨테이너라는것을 확인했다. 그래서 살펴보니 이렇게 사용할 수 있었다.


  • WPF XAML

    WindowsFormsHost Name="windowsFormsHost" />
    
  • WPF CS

    windowsFormsHost.Child = ...
    


그러니까 코드로 WinForm 컨트롤을 완성해두고 WindowsFormsHost에 넣으면 보여진다는 것. 그래서 한번 시도해보았더니, 잘 되었다. 아래 예제는 내가 작업했던 프로그램인데, WPF 프로그램안에 WinFormsHost를 넣고, 그 안에 차트를 넣어 표시한 것이다. 잘 꾸민다면 WPF와 WinForm컨트롤 사이의 위화감 없이 꽤 이쁘게 잘 나온다. (물론 꾸미기는 오지게 어렵다. 이유는 아래부터...)



WPF안에 WinForm Chart를 넣는 방법

그러면 이제 본격적으로 WinForm Chart를 WPF안에 넣어보자. 일단 생각해둬야 할 것이 WPF의 디자이너는 오로지 해당 컨트롤, 그러니까 WinForm Chart가 들어갈 자리만 만드는거고, 당연히 WinForm 디자이너를 쓸 수 없으니 코드로 Chart를 직접 만들고 WPF의 WindowsFormsHost에 붙혀야 한다. WindowsFormsHost는 기존 WPF 컨트롤처럼 Gird에 붙이고, 백그라운드 색상등의 설정이 가능하다. 단, 백그라운드 투명하고, Chart를 투명으로 해서 WPF Window나 Page의 백그라운드가 보이게는 안된다. 아마 Chart쪽에서 보는 백그라운드는 WindowsFormsHost가 만들아준 공간 안이기 때문에 그 안 정보가 없어 그런 듯. 자세한건 모른다. 디자인적으로 뭔가 하려고 하면 안되는게 간혹 나오는데 생각해보면 뭔가 이해가 된다.


사족은 그만하고 WPF쪽 코드는 다음과 같이 처리하면 된다.

    <WindowsFormsHost Name="chart">
    </WindowsFormsHost>


만약 어느 Gird에 넣고 싶고, 백그라운드를 설정해주고 싶거나 기타 다른 옵션을 주고 싶다면 기존 다른 WPF 컨트롤을 디자인 하는 것처럼 하면 된다. 디자이너 쓰면 편하다. 물론 이름도 원하는대로 설정해서 한 Window나 Page에 여러개의 WindowsFormsHost를 넣을 수 도 있다. 아래는 WindowsFormsHost를 Gird에 붙이고, 백그라운드를 설정해 준 WPF 코드이다.

    <WindowsFormsHost Name="chart" Grid.Row="1" Grid.Column="1" Foreground="{x:Null}"  Background="#FF2B2B2B">
    </WindowsFormsHost>


이렇게 간단하게 두줄이면 WPF쪽은 준비가 끝났다. 이제 남은건 이 chart WindowsFormsHost에 담을 Chart를 준비하는건데, 앞에서도 말햇지만 코드로 직접 짜줘야 한다. 예를 들어 위에서 본 예제처럼 만들려면, 우선 System.Windows.Forms.DataVisualization.Charting;를 선언해서 Chart 컨트롤을 사용할 수 있게 하고, Chart 객체를 하나 선언하고, Chart 컨트롤에 필요한 부분(ChartArea, Series, Legend등)을 구성해 준 다음, WindowsFormsHost에 넣으면 된다. 데이터까지 담으면 그래프가 완벽히 그려진다. 위에서 본 예제를 위해 만든 코드는 아래와 같다. 물론 이는 예제일 뿐이고, 실제 만든 프로그램에서 사용된 코드이므로 바로 사용할 순 없다. 참고만 하자. 직접 쉽게 만들고 사용하는 방법은 아래에 추가로 설명한다.

     private void graphInit(List<SubMcu> subMcuList)
    {
        Chart chartView = new Chart();
        Title title = new System.Windows.Forms.DataVisualization.Charting.Title();
        title.Text = "안전도";
        title.ForeColor = System.Drawing.Color.White;
        chartView.Titles.Add(title);
        chartView.BackColor = System.Drawing.Color.FromArgb(0, 0, 0, 0);

        ChartArea chartArea = new ChartArea();
        chartArea.Name = "Safety";
        chartArea.BackColor = System.Drawing.Color.FromArgb(0, 0, 0, 0);
        chartArea.AxisX.IntervalAutoMode = IntervalAutoMode.FixedCount;
        chartArea.AxisX.Interval = 1;
        chartArea.AxisX.TitleForeColor = System.Drawing.Color.LightGray;
        chartArea.AxisX.LineColor = System.Drawing.Color.LightGray;
        chartArea.AxisX.MajorGrid.LineColor = System.Drawing.Color.LightGray;
        chartArea.AxisX.MajorGrid.Enabled = false;
        chartArea.AxisX.LabelStyle.ForeColor = System.Drawing.Color.LightGray;
        chartArea.AxisX.MajorTickMark.LineColor = System.Drawing.Color.LightGray;
        chartArea.AxisY.LineColor = System.Drawing.Color.LightGray;
        chartArea.AxisY.TitleForeColor = System.Drawing.Color.LightGray;
        chartArea.AxisY.Maximum = 100;
        chartArea.AxisY.TitleForeColor = System.Drawing.Color.LightGray;
        chartArea.AxisY.MajorGrid.LineColor = System.Drawing.Color.LightGray;
        chartArea.AxisY.LabelStyle.ForeColor = System.Drawing.Color.LightGray;
        chartArea.AxisY.MajorTickMark.LineColor = System.Drawing.Color.LightGray;
        chartView.ChartAreas.Add(chartArea);

        Series series = new Series();
        series.ChartArea = "Safety";
        series.BackGradientStyle = GradientStyle.TopBottom;
        series.ChartType = SeriesChartType.Column;
        series.XValueType = ChartValueType.String;
        series.BackSecondaryColor = System.Drawing.Color.Aquamarine;
        series.LabelForeColor = System.Drawing.Color.White;
        series.Color = System.Drawing.Color.SteelBlue;
        series.IsValueShownAsLabel = true;
        chartView.Series.Add(series);

        Legend legend = new Legend();
        legend.Enabled = false;
        chartView.Legends.Add(legend);

        chart.Child = chartView;

        foreach (SubMcu subMcu in subMcuList)
        {
            chartView.Series[0].Points.AddXY(subMcu.Id, subMcu.SafetyFactor);
        }
    }


처음에는 저걸 직접 일일이 쳐 가면서 만들었다. 디자이너를 직접 사용할 수 도 없을 뿐더러, 후에는 조금 편법으로 학교 수업에서 배웠던 코드로 차트 컨트롤 만드는 예제를 통해 만든 다음 붙혀넣었지만 뭔가 제대로 나오지 않는 부분이 많았기 때문. 물론 하나하나 세세하게 신경써가면서 만드니 결국 잘 나오긴 하더라. 그래서 나는 저 코드를 메모장에 복사해서 그때그때 필요한 부분에 붙혀서 사용했는데, 후에 간단한 방법을 찾게 되었다. 그 방법은 디자이너를 이용하는것. 

WinForm 디자이너를 사용할 수 없다고 앞에 말했는데, 그냥 디자이너를 위한 새로운 프로젝트를 하나 만들어서 그기서 디자인 한 다음, 디자이너가 생성해 둔 코드를 복사해서 넣으니까 꽤 잘 돌아가더라. 물론 잘 안보이거나 안나오는것들은 일일이 수정이 필요하긴 하지만, 각각의 컨트롤을 직접 코드로 설정해주는 것 보다는 훨씬 쉬웠다. 단 디자이너로 만들 경우 주의할 사항이 있는데, 색상 설정등은 필이 직접 해 줄 것.


WinForm 프로그램에서는 컨트롤의 색성 설정을 해주지 않아도 기본 색상이 선택되어 잘 나오지만, WPF에 그대로 사용하니 나오지 않는 경우가 상당히 많다. 그러니 필이 기본값(비워져 있는등)인 것들은 직접 사용할 색상을 선택해주면 디자이너가 생성하는 코드에 그 색상이 지정되고, 결과적으로 WPF에서도 문제없이 나온다. 여튼 이렇게 상세하게 만든 Chart 컨트롤의 코드는 디자이너가 작성한 코드로 들어가 있다.(위 스크린샷에서는 아주 짧지만, 실제로 사용하려고 만들면 대략 50줄은 그냥 넘긴다)

이를 복사해서 사용할 곳에 넣어준 후 마지막에

    chart.Child = chartView;

를 써주면 잘 들어간다.(물론 각 객체의 이름은 잘 맞춰야 한다)

Minny_

,

[C#] C# DLL이 아닌 DLL파일 연결하여 사용하기

가우스메터 라는 장비와 연동하는 프로그램을 만들어야 한다

오랫만에 회사에서 연락왔다. 앞뒤사정 다 생략하고 결론부터 말하면 역시 뭔가 해달라는거였다. 가우스메터? 라는 장비랑 연동하여 주기적으로 값을 읽어 그 수치가 일정 이상일 경우 생 X랄발광을 하는 프로그램을 만들어야 한다. 물론 데이터 저장은 기본이고... 지X발광 하는거야 어렵지 않다고 생각했고, 데이터 저장도 별로 어렵지 않다고 생각했다. 늘 하는 방식이 있으니까. 근데 문제는 이 장비와의 통신은 USB여야 한다는 것.

사실 나는 프로그래머라기 보다는 그냥 인터넷에 있는 코드를 복붙해와 적당히 수정하고 적당히 작동되게 하는 그냥 그런 사람인데, USB 통신을 좀 도와달라는 부탁에 차마 거절은 못하고 일단 가서 확인해보겠다고 했다.


DLL 파일이 있긴 한데, C#에서 열리지 않는다.

해당 장비의 제조사에서 프로그램 개발하라고 DLL 파일을 제공했다. 일단 이걸 사용법을 알아야 하는데, 내가 알고 있는 사용법은 참조에 DLL파일을 넣는 것 정도... 당연히 그 방법을 먼저 시도했다. 안된다.

명세서를 보니 이 DLL파일은 C++로 만들어진거라 C나 C++(MFC), VB에서 제대로 작동하는듯, 예제 프로그램도 엑셀에 포함된 VB 메크로로 작동한다.

물론 DLL파일만 덩그러니 있고 나머지는 설명서 뿐이다. 뭐 드라이버류도 있고 한데 일단 개발에 필요한건 DLL파일이고 이를 사용할 방법인데..


DllImport("file")

기존 C++ 프로젝트들과 호환성을 고려하기 위한것인지, C#에는 이런게 있다. 바로 외부 DLL 파일을 코드에서 동적으로 활당하게 하는것. 물론 이것만 쓰면 안되고, 이 DLL 안에 있는 함수도 같이 정의해서 C# 프로젝트에서도 쓸 수 있게 해야 한다. 그러면 그 함수는 어디서 얻냐. 라고 하면, DLL 파일을 개발하라고 제공하면 그 DLL에서 쓸 수 있는 함수와 설명이 있는 문서도 같이 제공하니 그걸 참고하면 된다.


  1. 즉, 먼저 DLL파일과 설명서를 준비하고 설명서를 먼저 본다. 예시는 가우스메터 장비와 통신하기 위해 필요한 gm0.dll이라는 DLL 파일과 그의 설명서이다. 아래와 같이 문서 안에 어떤 함수가 있고 어떤 기능을 하는지가 정확히 적혀 있다. 개발환경에 맞는 자료형으로 만들어져 있을테니 당연히 C#에 맞는 자료형으로 적당히 수정한다.


  2. DLL파일은 프로그램의 exe 파일과 같은 경로안에 준비한다. 프로젝트 안에 넣으면 어떻게 될지 모르겠는데 내 생각에는 안될 것 같다. 그리고 위의 설명서에서 사용하고자 하는 함수를 보고 그에 맞춰 적는다. 예시에서는 장비와의 새로운 커낵션을 만드는 gm0newgm, 장비와 연결하는 gm0startconnect, 장비에서 값을 읽어오는 gm0getvalue 라는 함수 3개를 사용할 것이다. DllImport를 사용하려면 System.Runtime.InteropServices; 를 using 해야 하고, 클래스 선언후 밑에, 메소드 밖에 써주면 된다. 함수의 접근지정자도 맘대로 해도 된다. 단 static extern으로 선언해야 한다.(생각해보면 당연하다)


    using System.Runtime.InteropServices;
    
    [DllImport("gm0.dll")]
    private static extern int gm0_newgm(int port, int mode);
    
    [DllImport("gm0.dll")]
    private static extern int gm0_startconnect(int hand);
    
    [DllImport("gm0.dll")]
    private static extern double gm0_getvalue(int hand);
    
  3. 이제 원하는곳에서 해당 함수를 쓰고 동작을 확인하자. 아래 예제에서는 ERROR:-2 라고 표시됬는데 이는 장비와 연결할 수 없을때 나오는 에러 코드 중 하나이니 결과적으로 DLL파일의 연결은 잘 되었다고 할 수 있다. 만약 DLL파일이 없다면, GDI+ 예외가 뜨거나 아까 using으로 사용한 System.Runtime.InteropServices 예외가 뜨니, 이를 적절히 처리하면 될 것이다.



Minny_

,

[C#] 시리얼 통신 시, 수신 이벤트가 두번 이상으로 들어올 경우


이거 때문에 하루종일…

현장 실습에서 젤 어이가 없으면서 젤 당황스러웠던게 바로 시리얼 통신이다. 산업 현장에서 측정장비나 생산장비들이 가지는 정보를 PC나 다른 장비로 전달하기 위해서 시리얼 통신이 많이 사용되는데, 사실 시리얼 통신이라는것을 이때 처음 본 것이다. 기껏 본거야 안드로이드 폰에 펌웨어 올릴 때 가상 COM포트로 데이터 전송하는것 정도? 현장 실습이 반이상 지나서 시리얼 통신을 주구장창 만저본 결과, 뭐 정말 별 것도 아니었지만. 컴퓨터를 처음 만질때 부터 USB에 키보드와 마우스를 꼽았던 나로써는 솔직히 이게 뭔지… 조차 몰랐으니까.


처음 봤으니 당연히 이걸 어떻게 다뤄야 하는지에 대한 감 조차 오지 않았다. 근데 하라고 하니 일단은 해야지. 그래서 처음에는 진짜 아무것도 모르고 시리얼 통신 예제를 복붙하다시피 해서 요구사항대로 진행했었는데, 정말 얘기치 않은 문제가 발생하는것이다. 열심히 예제와 실제 통신하는것을 찾아본 결과 개념 자체는 어느정도 이해가 됬는데, 정작 프로그램에 적용해보니 뭔가 이상한 것이다.

결국 하루종일 그 문제에만 몰두했다. 이게 도대체 뭔지조차 감이 오지 않았고, 당장 시리얼 통신을 시물레이션할 수 있다는 것도 알지 못했으니 결국 실물로 측정기같은게 있어야 계속 진행할 수 있었는데 그걸 전혀 몰랐다. 그래서 원인을 사실 전혀 알지 못했었다. 뭔가 이상해서 안되긴 하는데 뭐가 문제인지를 전혀 몰랐다.


시물레이션을 해보니 알게 되었는데…

결국 시리얼 통신에 대해 깊이 알아보게 되었고, 시물레이션이 가능한 가상 시리얼 통신 시물레이터도 알게 되었고, 결과적으로 쓰는 방법 자체를 완벽히 알게 되었다. 그래서 테스트를 해 보니…

수신되는 데이터가 짤려 들어오는것이다


여러 C# 시리얼 통신 예제들은 수신시 이벤트가 발생하도록 되어 있다. 이 시리얼 통신의 수신 이벤트가 발생하면 수신 버퍼에서 데이터를 가져와 처리를 하게 된다. 문제는 이 이벤트가 한번 데이터 송신시 한번 되는것이 아닌, 두세번에 걸쳐 나눠 들어오는 것이다. 간단한 예를 들어보면…

동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라 만세
무궁화 삼천리 화려강산 대한사람 대한으로 길이 보전하세

라는 문자열 데이터를 시리얼 통신으로 받으면


동해물과 백두산이 마르고 닳도록 하느님이 보우하
사 우리나라 만세
무궁화 삼천리 화려강산 대한사람 대
한으로 길이 보전하세

라고 세번의 이벤트가 발생되고 이걸 합쳐야 원래의 데이터로 들어오는것이다. USB to RS232 통신이여서 그런건지, 아니면 C#에서 제공하는 시리얼 통신 예제의 문제인지, 아니면 송신하는 쪽의 문제인건지, 통신 설정의 문제인지는 정확히 알 수 없지만. 여튼 데이터가 나눠진다. 나눠지는 횟수나 데이터가 잘려지는 위치는 그때그때 랜덤하며, 이게 결국 비트로 들어오는 데이터다보니 합치는것도 잘 합쳐야 데이터가 깨지지 않는다. 그리고 아무리 잘 들어와도 조금씩 데이터가 깨지는건 덤


문제는, 내가 필요로하는건 측정 장비에서 송신하는 데이터를 모두 받은 다음 아스키 코드로 번역하여 문자열로 만들고, 완성된 문자열에서 특정 값들을 가져오는것. 가져와서 DB에 쏴주고 특정 값은 HTTP POST로 보내줘야 한다.

그래서 어떻게 할까 고민을 했었는데, 결과적으로는 야매스러운 방법이긴 했지만, 완성을 하였다.


데이터 수신을 받아 처음과 끝으로 구분

내가 한 방법은 이 소제목과 같다. 측정 장비는 정규화된 문자열을 보내준다. 그중에서 값 부분만 측정한 값을 채워서 보내주는 것이다. 예를 들어 전자저울 같으면,

''''''''''''''''''''
측정 정보
''''''''''''''''''''
측정 모델       어쩌구
측정 시간       저쩌구 s
측정 타입       어쩌구 type

''''''''''''''''''''
측정 결과
''''''''''''''''''''
최종 무개       저쩌구 kg

''''''''''''''''''''
측정 종료
''''''''''''''''''''

이런식으로 되어 있다는 것. 물론 원래 전자저울은 그냥 00.00KG. 딱 한줄 찍어 보내준다. 이건 그냥 예시를 위해…


그러니까 여기서 시작은 “측정 정보” 라는 문자열로, 끝은 “측정 종료” 라는 문자열로 구분할 수 있다는 것. 시리얼 통신을 쭈욱 받아 문자열로 계속 쌓으면서 시작 문자열이 확인이 되면 그 앞은 다 버리고, 그 뒤부터 다시 차곡차곡 쌓는다. 쌓다가 “측정 종료” 라는 문자열을 확인하게 되면 그 앞은 필요한 곳으로 전송하던지 저장하고 있던지 하고, 그 뒤에 들어오는 문자열은 버리는것.

좋은 솔루션이라고는 할 수 없다 하지만, 별다른 해결 방법이 안보이는데 뭐… 정확히는 RS232 통신 쪽 설정 문제인거 같은데, 나로써는 도저히 해결을 할 수 없어 보여 이런 방법이라도 적용해본거지.


코드 및 설명

앞에서 이미 다 설명했으니 관련된 부분의 코드만 간략하게 기록한다. 아무래도 현재 돌아가고 있는 프로그램의 코드고 내 실력이 형편없어 보기 난해할 순 있지만. 그래도 나는 이런식으로 해결했다는 의미로 남기는 것.

int offset = 0;
string startStr;
string endStr;


private void sPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            byte[] rxBuffer;

            if (offset == 0)
            {
                rxBuffer = new byte[4096];
            }

            string rxString = "";
            int intRecSize = sPort.BytesToRead;

            if (intRecSize != 0)
            {
                sPort.Read(rxBuffer, offset, intRecSize);
                offset += intRecSize;

                for (int iTemp = 0; iTemp < offset; iTemp++)
                {
                    rxString += Convert.ToChar(rxBuffer[iTemp]);    
                }

                if (rxString.Contains(startStr))
                {
                    rxString = rxString.Substring(rxString.IndexOf(startStr));
                    if (rxString.Contains(endStr))
                    {
                        program.dataReceived(rxString, "Receive");
                        offset = 0;
                    }
                }
            }
        }

시리얼 통신 예제 자체는 이미 인터넷에 많이 돌아다니고 있다. 다만 이 부분이 해결된 시리얼 통신 예제는 보이지가 않더라. 그러니까 이런 경우가 흔하지는 않다는 거고… 흔하지 않다는건 잘 해 두기만 한다면 이런 문제는 없다는 거겠지. 그래서, 시리얼 통신 자체의 부분은 생략한다. 해당 메서드는 시리얼 통신 예제에서 시리얼 데이터가 수신될 때 발생하는 이벤트 메서드이니 쉽게 찾을 수 있을 것이다.


여기에서 startStr과 endStr 변수는 통신의 시작과 종료에 정해진 문자열을 지정하면 된다. 코드를 간단히 보면 알겠지만 시리얼 통신으로 받는 족족 문자열로 변환한 후 시작 문자열과 종료 문자열을 검사해서 시작 문자열이 있으면 그 이전은 잘라내고, 시작 문자열과 종료 문자열 둘다 있으면, 특별한 작업을 하던지 다른 클래스로 보내던지(여기서는

program.dataReceived(rxString, "Receive");

라는 부분으로 program 클래스에 완성 문자열을 보내게 된다.) 하면 되고, 시작과 끝으로 셋트가 맞춰지면 offset을 0으로 하여 문자열을 초기화 하고 다음 시리얼 통신의 수신 대기를 하게 된다.

이렇게 해서 일단 필요하다고 하는 현장 두곳에다가 넣어놨는데, 지금까지 클래임 없이 조용한걸 보면 아마도 잘 작동하는듯.

Minny_

,

[C#] WPF 프로그램의 노티바(시스템 트레이) 아이콘 생성


그냥 바로 못한다


현장실습 중 만든 프로그램은 기본적으로 작동중에는 백그라운드에서 돌아야 한다. 라는 조건이 붙는데, 아래 글(2017/02/06 - [Tip/Develop] - [C#] WinForm 프로그램의 노티바(시스템 트레이) 아이콘 생성)과 같은 조건이다. 같은 C#이고, 전에 만들어둔 코드를 그대로 붙혀 두면 될 것이라고 쉽게 생각했었는데, 그게 아니었다. 당장 NotifyIcon 이라는 도구상자 컴포넌트가 없다.


이유를 알아보니, NotifyIcon이라는 컴포넌트는 System.Windows.Forms 안에 포함되어 있는 거고, WPF는 Forms를 Using하지 않으니 바로 사용할 수 없는 것. 간단하게 System.Windows.Forms을 Using하면 되지 않을까 라는 생각을 하지만, 그렇게 할 경우 MessageBox와 같은 System.Windows.Forms와 System.Windows.Controls에 동일하게 존재하는 객체들의 모호성이 생긴다.


결국은 그냥 NotifyIcon 객체를 직접 지시하여 생성해 주고 그 설정을 맞춰주면 된다. 사실 WinForm의 방법과 크게 차이는 없지만, 직접 지시를 해줘야 하는 부분이 있어야 한다는것.


배경이 똑같으니 목적이나 방법 또한 아래 글(2017/02/06 - [Tip/Develop] - [C#] WinForm 프로그램의 노티바(시스템 트레이) 아이콘 생성)과 거히 동일하니 뭘 할 건지는 해당 글을 먼저 보고 오는것을 추천.



그래서 하는 방법


매인 윈도우가 있는 클래스에 전역변수로 NotifyIcon을 직접 생성해 주고, init를 할 때 해당 객체를 적당히 맞춰준다. WinForm에서 생성하는것과는 다른 부분이 바로 이 부분이고, 그 뒤의 부분은 동일. 필요한 상황에 맞는 이벤트나 메서드에 NotifyIcon을 보여주고 창을 숨긴다. NotifyIcon에 더블클릭 이벤트를 걸어주고, 이 이벤트 안에는 NotifyIcon을 숨기고 창을 보이게 하는것.


1. NotifyIcon을 생성하고 설정

 public partial class MainWindow : Window{
    public System.Windows.Forms.NotifyIcon notify;

    public MainWindow(){
        InitializeComponent();
    }
    ...


전역으로 하나 생성해주고,


private void Window_Loaded(object sender, RoutedEventArgs e){
     notify = new System.Windows.Forms.NotifyIcon();
     notify.Icon = Properties.Resources.ico;
     notify.Text = "PLC 통신";
     notify.DoubleClick += Notify_DoubleClick;

     ...
}


Window가 로드될 때 이름과 아이콘, 그리고 이벤트를 지정해 준다. WinForm때와 마찬가지로 아이콘은 지정해주지 않으면 아무리 잘해도 보이지 않으니강조하는 이유는 내가 당해봐서… 꼭 빼먹지 말고 해주자.


2. 창이 숨겨지고 시스템 트레이 아이콘이 보이게

3. 시스템 트레이 아이콘이 숨겨지고 창이 보이게


는 사실 WinForm때와 동일하다. 2번의 경우 창이 닫길때나 숨겨야 할 상황의 메서드에 창을 숨기고 notifyIcon을 보이게 하면 되고, 3번의 경우에는 그 반대. 물론 그렇다고 WinForm의 코드를 그대로 복붙해서는 안된다. 창의 숨김, 보이기가 WinForm과는 다르니까 신경써주자.

 private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        { 
            if(SharedKeyword.isRunning == true)
            {
                notify.Visible = true;
                this.Visibility = Visibility.Collapsed;
                e.Cancel = true;
                return;

            }
private void Notify_DoubleClick(object sender, EventArgs e)
        {
            notify.Visible = false;
            this.Visibility = Visibility.Visible;
        }


Minny_

,

[C#] WinForm 프로그램의 노티바(시스템 트레이) 아이콘 생성



간단한것만…


현장실습 중에 만든 프로그램에서 가장 기본이 되어야 하는것이 작동중에는 백그라운드에서 돌아야 한다.


막 메뉴가 추가되거나, 아이콘이 상태에 따라서 바뀌거나 하는것이 아닌 간단한 정도만 구현하면 됬었다. 그러니까 순전히 프로그램이 작동중일때(미들웨어로써 역활을 하고 있을때) 프로그램 종료를 하면 종료가 되는것이 아닌

  1. 창이 숨겨지고 2. 시스템 트레이(노티바)에 띄워지도록하고 3. 노티바 아이콘을 더블클릭하면 다시 창이 보이며 시스템 트레이(노티바) 아이콘은 없어지도록

하는것.



위의 목적대로 만드는 방법


WinForm 프로그램은 만들기 쉽다. 도구 상자에서 NotifyIcon 을 가져와서, 아이콘과 이름을 지정한 후, 필요한 상황에 맞는 이벤트나 메서드에 NotifyIcon을 보이게 하고 창을 숨기면 된다. NotifyIcon의 더블클릭 이벤트를 생성해주고 이 이벤트 안에는 NotifyIcon을 숨기고 창을 보이게 하면 끝.



1.NotifyIcon을 생성한다


그냥 도구상자에서 찾아 끌어오면 된다. 그리고 해당 콤포넌트의 아이콘과 이름을 지정해준다. 귀찮으니 이름은 기본값으로 놔두었다. 아이콘은 없으면 안된다. 아이콘이 없으면 아무리 잘 해놔도 보이지 않는다. 아무 아이콘이나 대충 끌어와 리소스에 넣고 맞춰주자.


2.창이 숨겨지고 시스템 트레이 아이콘이 보이게

위와 같은 목적으로 하려면 프로그램이 종료되지 않게 해야 하므로, 해당 프로그램의 매인 From에 FormCosing 이벤트를 붙이고, 해당 메서드에 다음과 같이 작성하면 된다.


private void MoisturmMeasurmentProgram_FormClosing(object sender, FormClosingEventArgs e){
        if (!btnStart.Enabled)
        {
            e.Cancel = true;
            notifyIcon1.Visible = true;
            this.Visible = false;
            return;
        }
    }


물론 상황에 따라 저 if안에 검사할 조건을 바꿔야 한다. 나 같은 경우는 btnStart라는 버튼을 활성화, 비활성화 하는것에 따라 프로그램의 작동 상황을 구분하니 이렇게 한 것이고… 물론 이렇게 하면 안된다!

FromClosing 이벤트를 취소하기 위해 e.Cancel = true를 하였고, 시스템 트레이(노티바) 아이콘을 보이게 한 후, 이 From을 보이지 않게 한다.


3.시스템 트레이 아이콘이 숨겨지고 창이 보이게

창을 다시 띄우고 싶을때 해당하는 메서드에 동작을 넣어주면 된다. 소제목도 2번과 반대이고 로직또한 2번과 반대이니 어렵지 않다. 나는 간단하게 시스템 트레이 아이콘을 더블클릭하면 해당 동작이 되게 한다. 그러기 위해서는 시스템 트레이(노티바) 아이콘의 더블클릭 이벤트를 붙혀주면 된다.


이벤트 붙이는거야 뭐 속성에서 이벤트를 더블클릭해서 메서드 생성을 해주던지, 직접 코드에서 붙혀주던지 알아서 하면 된다. 아래는 해당 이벤트 코드이다.

private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e){
        this.Visible = true;
        this.Activate();
    }



Minny_

,

Windows 10(윈도우 10) 에서 디지털 라이센스(디지털 라이선스)로 정품 인증 받기

부제 : 노트북의 Windows 10 Home을 넷북의 Windows 10 Pro로 인증받기



개요

Windows 10 RedStone 1(윈도우 10 레드스톤 1, 윈도우 10 1주년 기념 업데이트) 에서 업데이트 된 디지털 라이센스(디지털 라이선스)로 정품 인증 받는 방법을 간단한 설명과 함께 적용해보는 포스팅입니다.



주의사항

  1. 단순히 디지털 라이선스(디지털 라이센스, 이하 디지털 라이센스)에 대한 내용과 디지털 라이센스 인증 방법에 대한 설명이 아닌, Windows 10 Home이 빌트-인 된 삼성 노트북을 Windows 7 Professional 에서 Windows 10 Pro로 업그레이드한 LG 넷북의 라이센스를 가져오는 과정에서 관련 내용을 설명합니다.
  2. 따라서 이 포스팅을 그대로 따라한다기 보다는 저는 이런 방법으로 해 봤으니, 여러분들은 포스팅을 참고하고 이해하여 디지털 라이센스로 인증 받는 방법을 적용하는걸 추천드려요.
  3. 어려운 내용은 아니나, 아무래도 소프트웨어의 라이센스를 변경하는 방법이니 만큼 위험성이 있습니다. 그러니 이 방법 적용으로 인한 시스템 문제나 피해, 법적인 문제등은 제가 일체 책임지지 않습니다.

포스팅을 읽고 그에 대한 정보를 얻는것을 위 주의사항에 동의하는걸로 관주합니다.



디지털 라이센스(디지털 라이선스)?

Windows 10 RS1(윈도우 10 레드스톤 1) 부터는 제품 키나 하드웨어 기반 인증(SLIC BIOS + SLP Key, MSDM)과 같은 방법 말고도 디지털 라이센스라는 인증방법이 추가됩니다. 이 라이센스는 기존 Microsoft Windows 제품을 소유하는 물리적인 방법(제품키를 소유하거나, 바이오스에 인증을 위한 코드가 있다거나)이 아닌 Microsoft 계정에 Windows 제품을 소유하게 하는 방법입니다. 장황하게 몇줄 되게 설명한거 같은데,

간단하게 말하면, Windows 10 TH2(Windows 10 ThresHold 2) 까지 제품 라이센스가 속해있던 곳과, Windows 10 RS1(Windows 10 RedStone) 부터 제품 라이센스가 속한 곳이…

기존 : 정품 인증서(COA), 제품 키(Product Key) 또는 하드웨어(SLP Key + SLIC BIOS), MSDM(OEM DM체널)
추가 : 마이크로소프트 계정(MSA)

이렇게 차이가 나게 됩니다. 이렇게 적어두면 이해하면 쉽죠? 블리자드 게임처럼 내 계정에 소프트웨어가 귀속되는 거예요.

Windows 10 이후의 넘버링 Windows는 없다고 하였고, 요즘은 단순히 특정 디바이스에 설치되어 실행되는것이 아닌 계정에 귀속되어 어디에서나 사용할 수 있는 것이(+ 기간제) 최근 소프트웨어 라이센스의 추세이기도 하니 Microsoft Windows 도 본격적으로 이렇게 바뀌나 봅니다.

다만, Windows 10 이전 운영체제에 대한 라이센스는 예전과 그대로 계정 귀속은 되지 않으니, 이는 Widnows 10 부터 해당이라고 생각하면 되요. 단 Windows 10으로 어떻게든 인증을 받았고 해당 Windows 10이 로컬 계정이 아닌 Microsoft 계정으로 등록이 되어 있으면 해당 Windows 10 은 등록된 Microsoft 계정에 귀속됩니다.

Microsoft 고객지원에 올라온 내용으로는 기존 정품 인증서나 제품키, 하드웨어 인증 방법은 그대로라고 되어 있지만, 제가 해 본 결과로는 이렇게 인증 받고 Microsoft 계정을 등록해두면 해당 Windows도 Microsoft 계정에 귀속되도록 되어 있어요. 기업용 LTS 라이센스나 특정 라이센스는 제한이 될지 모르겠지만, 일반 소비자용 라이센스는 Microsoft 계정에 귀속되는걸 확인하였어요.



Windows 10 Home이 설치된 노트북, 그리고 Windows 10 Pro가 설치된 넷북.

저는 삼성 노트북을 사용하고 있고 이 노트북에는 Windows 10 Home 이 설치되어 있어요. 이 블로그 어딘가 찾아보면 삼성 NT500R5L-Y77L 제품 리뷰 ( 2016/01/20 - [Review/Laptop] - 스카이레이크와 맥스웰을 탑재한 삼성 노트북5 NT500R5L - 시작 )가 있으니 많이많이 봐주세요 그리고 저는 Windows 7 Professional 을 가지고 있고, 이걸 LG 넷북에다 Windows 10 Pro로 업그레이드 설치 해 두었어요. 왜냐면, Microsoft가 Windows 라이센스를 계정 귀속으로 바꿀 껄 예상했기 때문…은 아니고 그냥 남아있는 라이센스 썩히기 싫어서 + 혹시나 모를 긴급 상황에 대비하여 설치해둔거예요.

그리고 넷북에 Windows 10 RedStone 1 업데이트를 하였고, 얼마 안 있어 노트북에도 Windows 10 RedStone 1 업데이트를 완료하였어요. 이렇게 업데이트 되었고 Microsoft 계정이 정상적으로 연결되어 있다면 제 Microsoft 계정에는 Windows 10 Home과 Windows 10 Pro 두 버전이 귀속되어 있는 상태가 되겠죠.

저는 노트북을 주로 쓰며 넷북은 정말 특별한 경우 아니고서는 거히 사용하지 않습니다. 그렇기 때문에 넷북에 Windows 10 Pro가 설치되어 있는건 조금 낭비겠죠. 더군다나 저는 Home 버전에 없는 Windows 기능 몇가지 때문에 이것저것 다양하게 사용하기에는 약간의 애로사항이 있었어요. 저번에 썼던 2016/07/10 - [Tip] - Windows 10, 8.1, 8 Home Edition에서 RDP(원격 데스크톱) 서버 기능 활성화하기 이 글도 어떻게서든 Pro 버전을 흉내내려는 짓이었으니까요.

그래서 이 디지털 라이센스로 제가 가진 노트북에 설치된 Windows 10 Home을 넷북에 설치된 Windows 10 Pro로 바꾸는 작업을 할겁니다. 이얏호!



진행


1. 제품 키 변경

현재 제 노트북에 설치된 에디션이 Windows 10 Home으로 표기된 것을 확인할 수 있는 스크린샷입니다. 이걸 Windows 10 Pro로 바꾸기 위해서는 재 설치를 하는 방법도 있겠지만, 라이센스를 Windows 10 Pro로 변경하면 갱신되면서 업그레이드 + 기능 추가가 됩니다. 저는 후자의 방법을 이용할 거예요.


그러기 위해서는 Windows 10 Pro의 제품 키가 필요로 하는데, 우선 제품키를 변경하기 위해서는 

제어판 - 시스템 및 보안 - 시스템의 Windows 정품 인증 항목의 제품 키 변경

를 해주면 되요. 그러면 아래와 같은 창이 뜨면서 제품 키를 입력하라 합니다. 물론 유니버셜 설정에서도 제품 키 변경을 할 수 있어요. 유니버셜 설정에서 제품키를 변경하기 위해서는

유니버셜 설정 - 업데이트 및 복구 - 정품 인증 - Windows 버전 업그레이드 항목의 제품 키 변경

을 해주면 되요. 둘다 똑같은거니 아무거나 하면 됩니다. 제어판 언제 없앨거냐? 마소야… 저는 캡처가 편한 유니버셜 설정의 제품키 변경으로 시작합니다.




Windows 10 Home에서 디지털 라이센스로 Windows 10 Pro로 업그레이드 하기 위해서는 Windows 10 Pro MSDM Product Key가 필요합니다. 이 제품 키는 Windows 10 Pro 키이며 이 키 자체적으로는 정품인증을 위한 키가 아닌 설치를 위한 일종의 더미 키 라고 생각하면 쉬워요. 이 키로 Windows 10 Pro가 설치 되면 Microsoft의 서버에 연결하여 하드웨어 ID를 확인하고 정품 인증 여부를 확인하는 거죠. 따라서 이 키는 Windows 7 Professional(또는 그 상위 에디션) 이랑 Windows 8.1 Pro에서 Windows 10 Pro로 업그레이드할때 자동으로 들어가는 제품키예요.

위에서 제품 키 변경을 눌러 제품 키 입력 창을 띄웠다면 Windows 10 Pro MSDM 제품키, 그러니까

VK7JG-NPHTM-C97JM-9MPGT-3V66T

를 입력해 줍시다. 제품 키를 입력함과 동시에 해당 키에 대한 유효성을 확인할 것이며, 일단은 Windows 10 Pro에 대한 인증키이니 곧바로 Windows 버전 업그레이드를 하겠다는 창을 띄웁니다.




2. 기다림과 업그레이드 완료.

업그레이드 시작을 하게 되면 잠시동안은 기다리면 됩니다. i7-6500U에 SSD가 설치된 PC에서 대충 커피한잔 할 정도의 시간이 걸렸으니 여러분들도 커피한잔이라도 하고 오세요. 시간이 딱 그정도예요.


업그레이드를 준비 한다는 창이 뜨고, 진행중이라는 에니매이션을 보여줍니다. PC 사양에 따라 다를 순 있지만, 이 과정은 그렇게 오래 걸리지는 않아요. 다만 이 다음은 재부팅 두번을 하게 되는데, 하는 중에 윈도우의 라이센스를 바꾸고, Home에는 없는 Pro의 기능을 추가하는 과정이 필요해서, 조금 오래 걸릴 수 도 있습니다.

약간의 시간이 걸린 후 재부팅이 되면 버전 업그레이드가 완료되었다는 알림을 띄웁니다. 저는 Windows 10 Home에서 Pro로 업그레이드 하였으니 유니버셜 설정이나 제어판의 시스템 정보가 Windows 10 Pro로 바뀐 모습을 확인할 수 있었어요. 그런데 제어판의 시스템 정보를 보니 정품 인증을 받지 않았다고 하네요.



3. Windows 10 다시 정품 인증

분명히 제 Microsoft 계정에는 Windows 10 Pro와 Windows 10 Home이 디지털라이센스로 귀속되어 있는데 왜 그럴까요. 당연히 이 노트북은 MSDM 서버에 Windows 10 Pro에 대한 하드웨어 ID가 없고 제 계정에도 이 노트북에 대한 Windows 10 Pro 디지털 라이센스가 없으니까요. Microsoft 계정에 귀속되는 Windows는 단순히 Windows만 귀속되는 것이 아닌, Windows와 PC가 한 쌍으로 귀속됩니다.

그러니까 다시 말하면 제 Microsoft 계정에는 넷북에 설치된 Windows 10 Pro와 노트북에 설치된 Windows 10 Home이 디지털라이센스로 귀속되어 있는거고, 정품 인증 프로세스에서는 이 노트북과 연결된 Windows 10 Pro 디지털 라이센스가 없으니 당연히 정품 인증이 안되는거죠. 이 사실은 유니버셜 설정의 업데이트 및 복구 - 정품인증에서 나타난 오류 메시지로 확인할 수 있어요.



저 같이 기기간 라이센스 이동(?)을 하려는 것이 아닌, Windows 재설치 등의 이유로 디지털 라이센스 인증을 받고 싶다면, 사실 이 과정에서 인증이 끝나게 됩니다. 활성화 정보에는

Windows가 Microsoft 계정에 연결된 디지털 라이선스를 사용하여 정품 인증되었습니다.

라고 표시가 될 것이구요. 다만, 저는 이 경우가 아니기에… 그래서 이제 제 Microsoft 계정에 있는 다른 PC(그러니까 넷북)의 Windows 10 Pro 디지털 라이센스로 인증 받도록 해야 해요.

유니버셜 설정 - 업데이트 및 복구 - 정품 인증 - 지금 Windows 정품 인증 항목의 문제 해결

을 누르면, 현재 정품인증이 왜 안되는지를 확인하고 그에 대한 해결책을 알려줍니다. 저 같은 경우는 이 디바이스랑 연결되는 라이센스가 Windows 10 Home 디지털 라이센스이니 다시 설치하던지, 아니면 Pro를 사던지 하라네요.


근데 한가지 또 다른 방법을 제시하는데, 이 메시지 창에 보면 ‘최근에 이 디바이스의 하드웨어를 변경하였습니다.’ 라고 클릭 할 수 있게 되어 있어요. PC의 메인보드나 특정 하드웨어가 바뀌면 하드웨어 ID가 변경되면서 정품 인증이 안되는 경우가 있는데 Microsoft에서는 이런 상황일때도 정품 인증이 가능하도록 이런 옵션을 추가로 지원합니다. 저는 이걸로 넷북의 하드웨어를 바꾸어서 이 노트북이 된 경우로 퉁(?) 치자는거죠. 이걸 클릭합니다.

클릭하면 큰 창이 반겨주면서 잠시 기다려 달라고 하고는 Windows 10 다시 정품 인증이라는 창을 띄워요. 여기에서 제 계정에 연결되어 있던 모든 디바이스를 표시합니다. 저 같은 경우는 Windows 10 Pro가 설치된 LG 넷북 하나가 있군요. 이 항목의 ‘지금 사용 중인 디바이스입니다.’ 를 체크한 후 정품 인증을 누르면, ‘Windows를 다시 정품 인증하는 중…’ 이라는 진행 에니매이션을 표시합니다.



그리고, 정품 인증이 완료되는 것을 확인할 수 있어요. 이후 시스템 정보를 표시하는 창들을 닫았다가 다시 열면 정품 인증을 받았다고 표시하게 됩니다. 혹시나 모를 상황에 대비하여 재부팅을 하는것도 좋겠죠.


4. 정품 인증 완료

결과적으로 정리를 하게 되면 저는

LG 넷북의 Windows 7 Professional
         - Windows 10 Pro 업그레이드 인증
         - Microsoft 계정 연결로 넷북 : Windows 10 Pro 을 계정 귀속
         - 삼성 노트북에 Microsoft 계정 연결
         - 하드웨어 변경으로 Windows 10 다시 정품 인증 시도
         - 삼성 노트북에 기존 LG 넷북에 연결된 Windows 10 Pro를 연결
         - 삼성 노트북에 Windows 10 Pro 정품 인증 완료

을 한 것이겠죠. Windows 라이센스 종류에 따라 이 방법이 적용되지 않을 수 는 있지만, Windows 10 무상 업그레이드로 Windows 10 라이센스를 가진 분들이라면 Microsoft 계정 연결만 제대로 되어 있다는 전제 하에 이렇게 디바이스간에 라이센스 이동이 가능합니다.






옳은 방법?

물론, 이게 정당항 방법일 것 같진 않아요. Windows 처음 사용자용 라이센스를 제외하면 사실상 대부분 하드웨어 귀속이며, Windows 라이센스가 디바이스에 따라가기 때문에 다른 디바에스에서는 해당 라이센스를 사용할 수 없어요.

Windows 10 다시 정품 인증 프로세스는 동일한 디바이스이긴 하나 수리 등으로 하드웨어 ID가 변경되었을 경우에 한해서만 인증을 받도록 지원하는 프로세스이며, 이는 기존에 하드웨어 ID 변경으로 인증이 안되던 문제를 전화로 동일한 디바이스라고 확인한 후 해결했던거와 동일하다고 생각하면 되요.

제가 한 방법은 동일한 디바이스가 아니기 때문에 사실상 이 프로세스를 사용하면 안되긴 하지만,,, 넷북에 놀고 있는 Windows 10 Pro도 어찌됬든 Widnwos 7 Professional 처음사용자용 라이센스에서 업그레이드 된 거였고, 결과적으로 노트북에서도 충분히 Windows 10 Pro로 인증 받을 수 있었으니까(그냥 귀찮아서 안했던 것 뿐이지…) 사용권 자체로는 문제 없을거라 생각해요.



끝!

결론은. 저는 디지털 라이센스를 활용하여 LG 넷북의 Windows 10 Pro 를 삼성 노트북으로 옮겨 봤어요. 뭐 이정도 적었으면 충분히 디지털 라이센스에 대해 이해할 수 있을거라 생각해요.

물론 이 글이 완벽히 옳은 정보는 아닐 수 있으며, 보다 자세한 정보는 Microsoft 고객 지원에서 상세하게 알려주고 있으니 여기서 찾아보는것을 추천드려요.(요즘은 뭐 고객센터랑 1대1 대화도 할 수 있으니까...) 이 포스팅은 어디까지나 하드웨어 ID 변경으로 인한 라이센스 이동의 예시니까요. 이번 포스팅은 여기서 마칠께요. 끝!


Minny_

,