[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_

,

Google GSuite 학생계정 간단사용 후기


Google GSuite


Google의 GSuite는 Google의 생산성관련 서비스인 Gmail, 문서 (Docs), 캘린더, 드라이브를 비즈니스용도에 맞게 개인 계정과는 다른, 별도의 플랜으로 제공하는 서비스이다. 소규모 프로젝트, 팀부터 기업, 학교 등 여러 방면에서 사용이 가능하고, Google 드라이브 기준 1인당 적게는 30GB부터 최대 무제한 드라이브 스토리지를 제공한다.

GSuite를 사용하는 단체 (학교 포함)는 단체 내 관리자를 통해 사용자에게 계정을 발급할 수 있으며, 사용자는 발급받은 계정을 PC, 휴대전화, 태블릿 등에서 이용할 수 있다. 사용자는 개인적으로 사용하는 계정이 아닌 독립적인 계정이기 때문에 개인용, 비즈니스용으로 용도를 구분하여 사용할 수 있다, 즉, 나눠진 계정은 서로의 영역을 침범하지 않기 때문에 캘린더나 연락처, 메모 등의 상호 동기화를 서로 막을 수 있다.


말년 대학생 OZ

필자는 아직 대학생이다. 4학년 2학기, 좋은 곳에 취직하고는 싶지만 아직은 공부가 몹시 하고싶은 막나가는 취준생이다.

얼마 전 재학중인 대학교의 이메일 서비스를 이용하려했으나 계정이 없다고 나온다. 어차피 많이 쓰지도 않고 교수님께 과제 제출용으로만 사용했었다. 이번에도 그랬다. 담당 교수님께 연구자료를 보내야할 일이 생겼고, 메일을 보내기위해 학교 이메일 서비스를 이용하려했지만 위와 같이 로그인은 되지 않았다. 결국 Gmail로 보냈다.

계정이 갑자기 날아간 이유에 대해 학교 전산실에 물어보았더니 Google로의 서버 이전으로 계정이 모두 초기화되었다고 한다. 사전에 공지는 오랫동안 했다고 한다. 그동안 필자가 군대에 가있었던게 문제다.

결국 마지막 학기이지만 새로 계정을 신청했다. 아래는 학생 신분으로 GSuite를 사용했을 때의 특징과 개인적인 의문점들을 적었다.

System) OZ는 학교 전산실에서 Google GSuite 계정을(를) 얻었다.
DesignOZ) 더 이상 캘린더나 연락처가 꼬여 중복 표기되는 문제는 이제 안녕이다.


특징


기존 구글 서비스를 그대로 이용가능하다.

일상생활 속에서 구글 서비스를 많이 이용한다. 특히 필자가 그러하다. 휴대전화와 태블릿은 안드로이드 OS, 연락처, 캘린더, 메일(Gmail)은 당연. 메모는 Google Keep, 간단한 문서작성, 스프레드시트와 PPT는 Google Docs, 개인 클라우드는 Google Drive를 이용해왔다.

이걸 그대로 PC에서 안드로이드에서, iOS에서 그대로 이용할 수 있다. 그냥 계정을 하나 더 추가하는 것 만으로도.


혼자가 아닌 팀으로 활동할 때 더욱 효율적이다.

캘린더의 공유가 가능하므로 미팅 등의 일정이 추가되거나 수정되면 팀원들에게 연동, 알림이 전송되어 언제든지 확인할 수 있다. 물론 자신이 추가하거나 수정할 수도 있다.

'난 못봤는데', '공지 안했잖아'는 씨알도 안먹힐 핑계다.


Google Drive 제공 용량이 개인 계정보다 크다.

Google GSuite의 가장 기본 플랜인 베이직 플랜의 제공량이 30GB이기 때문에 개인 Google 계정의 제공 용량인 15GB의 두배이다. 개인정보 보호의 날 이벤트를 통해 개인 계정의 용량을 2GB정도 더 키울 수는 있지만, 그래도 작은 것은 사실이다. 심지어 올해에는 안했다.

Google Drive의 제공 용량이 크기 때문에 Gmail 사용중 메일의 첨부파일도 개인 계정보다는 유연하게 처리할 수 있다.


Microsoft Imagine (구 DreamSpark) 인증을 간소화할 수 있다.

필자처럼 학생인 경우에 해당하며 사실 GSuite 계정이 아닌 일반 학교 메일 계정이라도 상관없다.

Microsoft사의 학생 지원 서비스인 Imagine (구 DreamSpark)의 인증이 간단해진다. Microsoft Imagine은 매년 자신이 학생이라는 인증을 해야만 이용할 수 있는 지원 서비스이기 때문에 국제 학생증 (ISIC)또는 Imagine 확인 코드가 없다면 매년마다 자신이 재학중이라는 문서를 업로드해야만 한다.

필자의 학교는 ISIC 국제 학생증카드 발급이 선택적이라 (일반 학생증 제공) 매년 인증을 받기위해서는 현재 자신의 시간표 또는 재학증명서 등의 인증을 거쳐야했다. 물론 지금은 GSuite 계정으로 Imagine 메일 인증이 잘 날아온다.


의문점


졸업 후에도 사용가능한가?

이건 학교 정책에 따라 다르다. 졸업 이후로는 사용을 못하는 학교도 있을 뿐더러 심지어 쉽게 제공하지 않는 학교도 있다고 들었다. 게다가 이제 대학교 말년인 필자는 졸업까지 약 6개월 가량 남았다. 졸업 후에도 사용할 수 있는지는 크나큰 관건이었다. 시한부 6개월짜리 계정이라면 잠시 파일백업용 임시계정이 될 것이 뻔하기 때문이다.

다행히도 학교에서는 졸업 후에 사고만 안친다면 지우지 않겠다고한다 (…)

이 사고가 무슨 사고를 의미하는지는 모르겠지만 적어도 개인 클라우드 서버를 구성하기 전까지는 얼마든지 사용할 수 있다.


무제한 제공 용량의 경우 표기는?

당연한 얘기지만 물리적인 디스크 드라이브처럼 표기 용량과 제공되는 용량이 차이가 있는 것은 아니다.

필자는 학교에서 제공하는 GSuite계정을 사용중이며 이것은 엔터프라이스 플랜(무제한 드라이브 스토리지)을 사용하고있다. Google Drive나 Microsoft OneDrive 등을 네트워크 드라이브로 마운트해주는 RaiDrive로 연동해보니 8EB (엑사바이트) 로 표기된다.

살면서 1PB(페타바이트)는 채울 수 있을까 싶을 정도인데 EB단위로 표기되니 입이 쩍 벌어질 정도이다.


아무리 채우고 채워도 눈금 하나 올라가지 않는다.


Google Photo는 사용가능한가?


16MP 이하 사진이라면 재압축이긴 하지만 기본 제공 스토리지 외에도 무제한으로 저장할 수 있는 Google Photo지만 아무리 무제한이라도 GSuite는 개인용 계정이 아니기 때문에 관리자가 Google Photo등의 서비스는 사용 허가를 해주지 않으면 이용할 수 없다.

필자의 경우가 이러한데 심지어는 개인용 계정에서 백업을 위해 Google Drive내 ‘Google 포토’ 폴더를 그대로 복사한다고 해도 Drive나 Photo에서 볼수 없다. 아예 폴더 자체가 사라져버리기 때문에 복사하는데 몇시간을 투자했는데 한순간에 사라져 어이가 상실된 자신의 표정을 복사했던 시간만큼동안 하염없이 보고있을 수도 있다. 실제로 그랬다.

필자의 2013년도부터 2017년 8월까지의 사진 5600여장은 그렇게 잿더미가 될 뻔했다.


Google Play, Youtube 등의 서비스도 사용가능한가?

Youtube는 로그인이 안된다. Google Play는 로그인은 되지만 정상적인 사용은 불가능했다.

된다 하더라도 오히려 개인 계정을 쓰는 것이 좋다.

여담

GSuite 홈페이지의 문의하기 버튼. 문의하기 버튼을 통해 딜(Deal)을 할 수 있을지는 의문이다.

'News > Daily life' 카테고리의 다른 글

CJ 헬로모바일에서 M 모바일로 갈아탑니다. (1)  (0) 2017.02.05

DesignOZ

,

Xiaomi VoLTE CallLog Changer (FIXX)

샤오미 스마트폰 중 퀄컴 스냅드래곤 82X 프로세서를 사용하는 디바이스 (Mi 5, Mi 5s, Mi 5s Plus, Mi Note 2, Mi Mix)에서 AOSP 또는 LineageOS기반 롬을 사용했을 때 VoLTE로 전화를 수신했을 시 아래와 같은 오류가 발생하며,

이 문제는 샤오미 MIUI에서는 발견되지 않는 시스템적 문제이므로 본질적인 문제는 애플리케이션단에서는 해결할 수가 없지만 임시방편으로 통화목록을 수정한다던가 하는 방법은 얼마든지 가능하므로 이 앱을 만들게되었습니다.

오류 목록

  • 전화 수신시 발신자번호뒤 VoLTE를 사용중인 자신의 번호가 붙어서 나타나는 오류가 발생하는 오류
    (ex - 0101234567801011223344)

  • 전화 수신시에는 문제가 없었지만 전화기록에는 위와 같은 오류가 발생하는 오류

  • 위의 두가지 오류가 모두 발생하는 경우

  • 전화를 수신했음에도 통화기록에는 부재중 전화로 기록되는 오류 (17.08.13 확인)


기부, 피드백

도네이션(기부) 또는 이곳에 명시되지 않은 버그 발견 알림은 아래의 링크로 연락주십시오.

Team OverImagine 블로그
Team OverImagine 페이지
트위터@Today_OZ
카카오톡@Design_OZ


스크린샷


0.995 (ver.20170608_3)


변경사항


0.995 (20170821_build 178)

오버레이 기능 추가
수신 전화 종료후에도 부재중전화로 저장되는 오류 발견, 해결

이스터에그, 광고 제거
디자인 변경
실행중인 서비스를 사용자가 종료할 수 있도록 변경
메모리를 정리했을 때 서비스 종료되는 문제 수정
부재중 전화 알림 클릭시 통화기록으로 넘어가도록 수정
그 외 알림 클릭시 앱으로 넘어가도록 수정


다운로드

주의

이 앱은 통화목록 중 VoLTE 버그로 인한 오류항목을 변환합니다. 중요한 통화기록이 있는 경우 미리 백업해주십시오.
이 앱으로 인한 피해는 개발자가 책임지지 않습니다.

변경사항이 많아 이전 버전에서 설치시 앱 제거후 새로 설치할 것을 권장합니다.

Google Play


'Project > CallLog' 카테고리의 다른 글

Xiaomi VoLTE CallLog Fix (FIXX)  (2) 2017.06.08

DesignOZ

,

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


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_

,

저렴한 가격으로 즐길 수 있는 블루투스 4.2 이어폰, 소닉기어 Bluesports1


블루투스 이어폰

요즘은 블루투스로 연결되는 제품이 많습니다. 블루투스 이어폰을 사서 음악을 듣거나, 통화용 이어폰으로 한쪽귀에 꽃아서 쓰는 것은 이제 기본이고, 제 차에서는 안되서 많이 슬프지만차량의 오디오와 연결하여 스마트폰의 음악을 듣거나, 전화를 받을 수 도 있게 됨은 물론, 블루투스를 통한 오디오 스트리밍 성능이 좋아져서 일반 스피커류에도 블루투스 신호를 받아 선의 제약 없이 자유롭게 음악을 들을 수 있게 되었어요. 블루투스를 통해 스마트폰의 모바일 네트워크를 PC로 받거나, 웨어러블 디바이스와의 통신을 위한 용도, 블루투스 HID(키보드 마우스)용도로도 많이 사용되고 있어요.

많은 사용법이 존재하지만 블루투스가 발전하면서 꾸준히 같이 발전해온 것은 바로 블루투스 이어폰이라고 할 수 있어요. 저는 오래전부터 블루투스 이어폰을 썼었는데, 제일 처음 쓴 것은 클립컴의 HCS-100인가 아닌가... 여튼 였는데, 귓구멍 안에 들어가는 이어폰이 아니라 귀에 걸어두고 쓰는 클립형 이어폰이었어요.바로 그 유명한 오타쿠 이어폰 그당시 블루투스 소모전력이 많기도 하고, 블루투스 모듈이 소형화 되긴 어려우니 한쪽 이어폰에는 베터리가 들어가고 반대쪽 이어폰에는 블루투스 모듈이 들어가는 구조로 되어 있는 제품이었어요. 그걸 상당히 오래 썼답니다. 그리고 브리츠의 블루투스 이어폰을 썼었기도 했고, 요전에도 어느 제품을 구매해서 썼었구요. 워낙 오래전부터 블루투스 무선이 얼마나 편리한지를 알고 있었기 때문에 블루투스 이어폰을 계속 써왔었어요.


소닉기어 Bluesports1를 샀어요.

앞에서도 말했지만 최근 3개월 전쯤인가, 중급형의 블루투스 이어폰을 구매한 적이 있었어요. 아주 음질도 좋았고, 베터리도 꽤 오래가고 귀도 편했구요. 하지만 방수가 되지 않아 세탁기에서 두번이나 세탁한 후 이상동작을 하며 고장이 나버려 얼마 쓰지도 않고 버렸어요. 어차피 제가 음악을 주로 듣는 편은 아니고, 장거리를 가는 대중교통 안에서 주로 썼었기 때문에 없더라도 큰 불편은 없었기에, 그리고 이제는 제 차가 생겨서 차량의 오디오 안에 음악이 있지 스마트폰에는 음악이 없거든요. 아깝기는 하지만 굳이 사야 하나 라는 생각으로 있다보니 그 뒤로 블루투스 이어폰을 살 생각이 없었어요.



근데 왜 샀냐? 라고 하면, 쌌어요. 정말정말 쌌어요. 어느날 제가 가볍게 보는 커뮤니티에서 특가가 떴다고 하는 글을 보고 들어가니, 블루투스 이어폰을 1+1으로 판매하고 있더라구요. 혹시나 나중에 필요할 지도 모르고, 하나는 친구한테 줘서 다른거랑 바꿔 먹어야지... 라고 생각이 들 정도로 저렴한 가격 17,400원 이었습니다. 제품 설명상 음악 재생시간이 짧은 편이긴 하지만, 가격대를 생각하면 충분히 납득할만한 부분이었고, 다른 이어폰과 비교해서 가벼운 축에도 들고, 일단 늘 쓰던 블루투스 이어폰이 없는 상황에서, 일단 하나 정도는 있어야 하지 않겠나 싶은 생각으로 그자리에서 바로 충동구매를 해 버렸습니다.

그렇게 소닉기어 Bluesports1을 사게 되었습니다.





상당히 품질이 불량하지만 가격에 눈 감아 줄게요.

1+1 이벤트로 사실상 블루투스 이어폰 하나를 8,700원에 구매한 것이나 다름 없는데, 사실 이 제품의 실제 가격은 22,400원, 오픈마켓에서 기본 할인쿠폰이 적용된 가격은 대체로 17,000원 선 으로 다나와에 등록된 제품 기준 2만원 초반대 블루투스 이어폰은 어느정도 이름을 가지고 있는 회사의 저렴한 제품군들로 구성되어 있는 상황이며, 소닉기어랩이라는 이 제품을 출시한 회사는 2017년 8월 5일 기준 최근 1년동안 저렴한 블루투스 해드폰 2종(에어폰 시리즈)과 이어폰 3종(BlueSports 시리즈)을 출시한 회사입니다. 제품이 5종 밖에 없으니 하나하나 상품 후기를 보기에는 어렵지 않았는데, 의미가 없어보이는 악평과 대충대충 쓴 평가들은 다 넘어가고 의미 있는 평을 보자면 품질이 좋지 않다는 이야기가 많이 나오는 편이라는게 좀 불안하긴 하네요.



소닉기어 Bluesports 1을 직접 수령하고 열어본 후로는 품질이 좋지 않다는 이야기가 왜 나오는지를 알 수 있었습니다. 제품의 특징이니 구성품이니등등은 일단 넘어가고 품질에 대해 먼저 언급해야겠네요. 한눈에 보이는 것은 2017년 물건이라고는 할 수 없는 퀄리티의 제품이예요. 이어해드 부분의 초음파 접합 부분은 잘 다듬어지지 않은 상태에서 진행된것 같고, 재사용한 것 같은 듯한 느낌의 플라스틱 자체 얼룩도 있으며 이어해드? 이어폰의 본체와 그물망을 고정하는 겉부분의 플라스틱 색상이 미묘하게 다릅니다. 본체와 이어폰간에 연결된 선도 상당히 얇은게 불안해보여요. 제품에 인증로고와 회사 로고등은 인쇄되어 있는데,,, 반쯤 지워져 알아볼 수 없습니다. 정말 제품의 퀄리티 하나는 어떤 의미에서놀라웠습니다. 확신할 순 없지만, 재사용 한 제품으로 생각 들기도 해요.


8,700원짜리다 생각하고 천천히 보면,

하지만 저는 저걸 8,700원에 구매했었으니, 그 시선으로 다시 보자면, 그래도 아쉬운 품질은 어쩔 수 없다고 생각하고 넘어갑니다. 단순히 까기 위해 리뷰를 작성할려고 한 건 아니었으니까요.

블루투스 이어폰 치고 오픈형 제품은 별로 없습니다. 애초에 요즘 제품들이 오픈형은 별로 없더라구요. 아무래도 오픈형은 외부로 소리가 새어 나오기도 하고 귀에 딱 고정되지도 않을 뿐더러, 귓구멍이 작은 사람의 크기에 맞출 수 없기도 해서 커널형 이어폰들이 대부분이고, 그런 이유로 블루투스 이어폰은 더더욱 커널형 제품이 많이 출시되고 있습니다. 그에 비해 소닉기어 Bluesports 1은 몇없는 오픈형 이어폰인데, 모양 자체는 사실 어디선가 많이 본 듯 하죠. 여튼 커널형은 아무래도 귓 속에 들어가도록 되어 있기 때문에 커널형을 싫어하는 분들도 분명히 있으실텐데 그런분들에게는 나쁘지 않을 만한 구성이기도 합니다.



무개는 든게 없어서 그런지 상당히 가볍습니다. 블루투스 모듈이 포함된 리모컨 부분의 무개감은 다른 제품들에 비해서 많이 가벼운 편이고, 이어해드부분도 귀에 전혀 무리가 가지 않을 정도로 상당히 가볍습니다. 제품 설명상 11g 이라고 하니 위에 취소줄은 없애도 될 듯. 블루투스 4.2를 채용한 제품으로 저전력으로 구동이 가능해 베터리 용량이 상당히 작은 편이니 블루투스 모듈 부분의 무개가 가벼울 것이고, 아무래도 가격대가 낮으니 이어해드 부분의 처리가 조악한 것 때문에 이어해드 부분의 무개가 가벼운 것이 그 이유라고 할 수 있겠네요. 그외의 선도 상당히 얇고 가벼운 제질인 것도 한 몫. 지금까지 출시된 블루투스 이어폰 중에 제일 가벼운 제품이 아닐까 합니다.



구성은 충전용 짧은 USB 케이블, 사용 설명서 영문, 사용 설명서 한글, 이어 플러그, 본품으로 되어 있는데, 충전용 USB 케이블의 퀄리티는 꽤 좋은 편이고 사용 설명서는 짧게 제품을 켜는 방법과 제품의 각 버튼들 기능 설명정도, 본품은 블랙과 화이트, 두가지 색상이며 이어 플러그는 이어해드에 씌우도록 되어 있습니다. 패키지 자체와 구성에 대해서는 다른 제품과 크게 다르지 않네요.



블루투스 모듈이 들어가 있는 조작부는 버튼의 눌림이 그렇게 빡빡하지도 않고, 그렇게 가볍지도 않습니다. 딱 적당한 수준으로 생각되고, 조작 상단부는 소닉기어사의 로고가 인쇄되어 있고, 그 아래 상태 LED 표시를 위한 구멍과 마이크 구멍이 존재합니다. 각 버튼은, 뭐 설명하지 않아도 대충 아실거라 생각합니다. 우측면 하단에는 Micro B 타입의 충전 단자가 있는데, 충전 단자를 막는 캡은 좀 부실한 편, 캡이 닫긴 상태에서 고정할 부분이 거히 없어 정말 꾹꾹 누르지 않으면 살짝 열린 상태로 있게 되네요. 파란색과 빨간색 LED로 제품 상태를 표시하게 됩니다. 


음악을 들어보자.

제품 마감 품질이 어떠하고, 뭐가 부족하고 라고해도 이 제품은 이어폰이기 때문에 실제 성능은 음악, 소리를 듣는 것으로 알아봐야 하는데, 저는 소리에 대한 조예가 굉장히 앝은 편이기 때문에(음악은 들리면 된다!) 그냥 제가 느낀 그대로를 적어봅니다.

SBC 코덱으로 수신받기 때문에 삼성 갤럭시 스마트폰과 같이 블루투스로 음원 재생 시 낮은 비트레이트로 음원을 전송하게 되면 아무리 좋은 블루투스 이어폰이라도 음질이 좋지 않게 됩니다. 물론 이어폰쪽의 품질이 좋다면 어느정도 들을만한 음질이 되기도 하고, 수신측에서 이를 후처리하기도 하지만, 이 제품은 그런 것은 없겠죠. 이런 차이점으로 인해 페어링 기기는 2대를 준비했습니다. 첫번째는 주로 쓰는 갤럭시 노트 FE, 그리고 두번째는 SBC 코덱으로 고 비트레이트를 사용하도록 시스템이 수정된 AOSP롬이 설치된 베가 아이언2로 준비하였습니다.




먼저 갤럭시 노트 FE에서 음악을 들어봤었습니다. 일단 가격대부터가 말해주듯이, 좋은 소리는 절대 아닙니다. 저음은 쿵쿵쿵이라기 보다는 푹푹푹하는 듯한 소리, 다시말하면 진동판이 굉장히 약해 저음이 힘이 없는 편입니다만, 완전  중고음때문에 저음이 뭉쳐 들리는 수준은 아니고, 어느정도 분리는 되는 수준, 반대로도 마찬가지로 중고음이 저음 때문에 떨려 들리는등의 완전 싸구려 라디오 이어폰 수준은 아니라고 할 수 있습니다. 정말 가볍게, 소리를 즐기지 않고 음악을 즐긴다면 완전 나쁘지는 않다고 생각합니다.

베가 아이언2에서 똑같이 음악을 들어봤었는데, 결론을 말하면, 크게 차이 없습니다. 비트레이트가 높아지면서 코덱에서 생기는 음질손상이 어느정도 없어졌구나 가 아주 약간 느껴지는 정도, 그러니까 아무리 고비트레이트 고음질로 재생이 되더라도 이어폰 유닛이 좋지 않아 결과적으로는 똑같은 상황입니다. 뭐 블루투스 베터리 표시가 되는 것은 확인할 수 있었네요. 음원을 재생하는 기기에서 이퀄라이저나 음질 최적화 기능이 제공된다면, 사용하는것이 훨씬 좋은편입니다. 사실 이 이어폰에서 큰 문제는 과도한 역V자로 셋팅된 듯한 소리인데 이를 다시 원래대로 잡아준다면 아주아주 나쁜편은 아니라고 생각되니까요.


결론은?

단도직입적으로 말하자면 좋은 제품은 아닙니다. 본인이 조금이라도 음질에 신경을 쓰는 사람이다. 라고 한다면 싸다고 샀다가 정말 바로 버릴 수 도 있는 물건. 마감처리등의 기본적인 품질조차 나쁜편입니다. 저도 뭔가 쿵쿵 울리고 선명하고 깔끔한 고음질을 좋아하기 때문에 음질에 대해서는 상당히 실망했고, 패키지를 풀었을때의 조악한 품질에는 당황스러움을 넘어, 돈 좀 더 주고 좋은 거 하나 살껄. 이라는 생각을 가지게 만들었습니다.

그래도 잘 쓰지도 않을거, 혹시 몰라 산 이어폰이기 때문에 비상용으로 쓴다라고 생각한다면, 가격도 저렴하고 블루투스 이어폰이고, 그래도 완전 싸구려 라디오 이어폰은 아니니, 그것들은 감안한다면 굳이 엄청 나쁜 제품은 아니라고도 할 수 있습니다. 베터리 유지시간이 짧은 대신 상당히 가벼워 부담도 덜하다는 것은 등가교환, 근래 보기 힘든 오픈형 이어폰이기 때문에 음질이 나쁘더라도 주변 잡음이 많은 대중교통등에서는 크게 차이 없을 수 도 있으니 넘어갈 수 있는 부분이라고 친다면 남은 부분은 품질이 조악한 것인데, 이는 가격이 아주 싸다는것으로 퉁 칠 수 있으니.



다만, 이 1+1 구성 행사일 때에는 나쁘지 않은 제품이라고 생각할 수 있지만, 정가, 혹은 오픈마켓 할인가로 구매한다고 생각하면, 그건 아닌것 같아요. 그 가격대에 어느정도 이름을 가진 회사들의 저렴한 제품군들도 많고, 또 제품 품질이나 음질면에서도 이거보다는 훨씬 좋을 것이니까요. 1+1 할때의 1개 가격으로 팔지 않는다면 아마 소비자들에게 외면 받기 아주 좋은 제품이 아닐까 합니다. 같은 제품군으로 Bluesports 2와 3도 있는데 당장 기본 가격대는 비슷하고 커널형, 그리고 색깔 또한 이쁘니 Bluesports 1은 정말 애매한 포지션의 제품이지요. 그러니 오픈마켓 1+1 행사도 했겠지요 여로모로 아쉬운 제품이라고 생각합니다.

끝.

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_

,

Xiaomi VoLTE CallLog FIX (FIXX)

개요

퀄컴 스냅드래곤 82X 프로세서를 사용하는 샤오미 디바이스 (Mi 5, Mi 5s, Mi 5s Plus, Mi Note 2, Mi Mix)에서 AOSP 또는 LineageOS기반 롬을 사용했을 때 VoLTE로 전화를 수신했을 시 아래와 같은 오류가 발생합니다.

  • 전화 수신시 발신자번호 뒤에 자신의 VoLTE 번호가 붙어서 나타나는 오류가 발생하는 경우.
  • 전화 수신시에는 문제가 없었지만 통화기록에는 위와 같은 오류가 발생하는 경우.
  • 두가지 오류가 모두 발생하는 경우.

이 문제는 운영체제와 관련이 있기 때문에 애플리케이션으로는 해결할 수가 없지만 임시방편으로 통화목록을 수정한다던가 하는 방법은 얼마든지 가능하므로 이 앱을 만들게되었습니다.


기능

  • VoLTE를 통한 전화 수신 이후 발생하는 오류 해결
  • 듀얼심 활성화시 SIM 1, SIM 2 전화번호 가져오기
  • 자동 변환을 통한 부재중 전화 확인 가능.


스크린샷





test 0.6 (ver.20170608_3)



변경사항


20170618_89

앱 최적화
듀얼심 사용시 SIM 2 변경이 안되던 문제 해결
자동변환 기능 추가


20170608_4

앱 이름 변경 (FIXX)
안드로이드 6.0.1 지원
스플래시 로고 위치 변경
이스터에그 추가


20170608_3 (test)

테스트버전 첫 배포


다운로드

주의

이 앱은 통화목록 중 VoLTE 버그로 인한 오류항목을 변환합니다. 중요한 통화기록이 있는 경우 미리 백업해주십시오.

이 앱으로 인한 피해는 개발자가 책임지지 않습니다.

Google Play


주의

이 앱은 통화목록 중 VoLTE 버그로 인한 오류항목을 변환합니다. 중요한 통화기록이 있는 경우 미리 백업해주십시오.

이 앱으로 인한 피해는 개발자가 책임지지 않습니다.


'Project > CallLog' 카테고리의 다른 글

FIXX 0.995 (2017.08.21_build178) 배포  (2) 2017.08.21

DesignOZ

,