안녕하세요? 맨날맑음 입니다.

윈도우에서 [Ctrl + Alt + Del]키를 누르면 아래와 같이 Windows 작업관리자를 볼 수 있습니다. 응용 프로그램 탭을 보면 현재 실행 중인 프로그램의 목록을 확인 할 수 있는데요. 이와같이 프로그램을 만들다 보면 특정한 프로그램을 선택하는 창을 제공해야 할 때가 있습니다.
이런 기능을 만들기 위해서는 Win32 API의 도움을 받아야합니다.(.net class library에서 지원해 주는지는 잘 모르겠습니다. 아시는분은 꼭 알려주시길..) 잡설이지만 .NET의 Windows Form 개발시 자꾸만 Win32 API를 사용하게 되는군요'' 분명 그에 매칭하는 .NET Class가 있을꺼라는 생각이 드는데요(일부 크리티컬한 기능 제외);

Win32 API의 EnumWindows 함수는 현재 실행중인 프로세서들의 목록을 얻어올 수 있는 기능을 제공합니다.
http://msdn.microsoft.com/en-us/library/ms633497(VS.85).aspx 이 링크에서 보다 자세한 MSDN 도움말을 확인 할 수있습니다.

C#에서 사용시에는 다음과 같이 선언 해 주어야 하는데, 한가지 고민되는 사항은 첫번째 인자가 callback 함수의 '함수포인터'를 넣어야 되는 것입니다. 두번째 인자의 의미는 사용자 임의의 데이터를 넘길 때 사용합니다.
[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool EnumWindows(EnumWindowsCallback callback, int extraData);

그런데 C#에는 함수포인터가 없기 때문에 이를 대체하기 위한 수단으로 'Delegate'를 사용해야 합니다.

그래서 아래과 같이 EnumWindows의 Callback 함수와 형식이 일치하는 Delegate를 선언해 줍니다.
public delegate bool EnumWindowCallback(int hwnd, int lParam);
배웠으니 시험도 해볼겸 간단한 예제를 만들어 보겠습니다. Windows Form 프로젝트를 생성합니다.
Form위에 프로그램의 목록과 아이콘을 띄워줄 ListView를 가져다 놓습니다.

각자 입맛에 맛게 속성을 조정해 줍니다. 저는 Form크기에 맞게 딱 붙도록 Dock속성을 Fill로 변경해 보았습니다.
코드(form.cs)로 가서 아래의 코드를 추가합니다.
using System;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace Sample
{
    public partial class Form1 : Form
    {
        public delegate bool EnumWindowCallback(int hwnd, int lParam);
        
        [DllImport("user32.dll")]
        public static extern int EnumWindows(EnumWindowCallback callback, int y);

        [DllImport("user32.dll")]
        public static extern int GetParent(int hWnd);

        [DllImport("user32.dll")]
        public static extern int GetWindowText(int hWnd, StringBuilder text, int count);

        [DllImport("user32.dll")]
        public static extern long GetWindowLong(int hWnd, int nIndex);

        [DllImport("user32.dll")]
        public static extern IntPtr GetClassLong(IntPtr hwnd, int nIndex);

        const int GCL_HICON = -14; //GetWindowLong을 호출할 때 쓸 인자
        const int GCL_HMODULE = -16;
        ImageList imgList;//ListView의 Image로 쓸 리스트
               

        public Form1()
        {
            InitializeComponent();
            imgList = new ImageList();
            imgList.ImageSize = new Size(16, 16);
            listView1.SmallImageList = imgList;
            listView1.View = View.List;
            //CallBack 델리게이트 생성
            EnumWindowCallback callback = new EnumWindowCallback(EnumWindowsProc);
            EnumWindows(callback, 0);

        }
       
        public bool EnumWindowsProc(int hWnd, int lParam)
        {            
            //윈도우 핸들로 그 윈도우의 스타일을 얻어옴
            UInt32 style = (UInt32)GetWindowLong(hWnd, GCL_HMODULE);
            //해당 윈도우의 캡션이 존재하는지 확인
            if ((style & 0x10000000L) == 0x10000000L && (style & 0x00C00000L) == 0x00C00000L)
            {
                //부모가 바탕화면인지 확인
                if (GetParent(hWnd) == 0)
                {
                    StringBuilder Buf = new StringBuilder(256);
                    //응용프로그램의 이름을 얻어온다
                    if (GetWindowText(hWnd, Buf, 256) > 0)
                    {
                        try
                        {
                            //HICON 아이콘 핸들을 얻어온다
                            IntPtr hIcon = GetClassLong((IntPtr)hWnd, GCL_HICON);
                            //아이콘 핸들로 Icon 객체를 만든다
                            Icon icon = Icon.FromHandle(hIcon);
                            imgList.Images.Add(icon);                            
                        }
                        catch (Exception)
                        {
                            //예외의 경우는 자기 자신의 윈도우인 경우이다.
                            imgList.Images.Add(this.Icon);
                        }
                        listView1.Items.Add(new ListViewItem(Buf.ToString(), listView1.Items.Count));
                    }
                }
            }
            return true;
        }    
    }
}

소스가 좀 많아 보이긴 하는데, 복잡하진 않습니다. Win32API들을 사용하기 위해 EnumWindows, GetParent, GetWindowText, GetWindowLong, GetClassLong을 선언해 줍니다.
ListView의 Image를 넣기 위한 ImageList도 추가합니다.

생성자에서는 ImageListListView의 초기화를 하고, Delegete를 생성하여, EnumWindows를 호출해 줍니다.
그럼 OS는 현재 실행중인 프로세서를 EnumWindowsProc으로 전달해 주게 됩니다. 단 EnumWindowsProc으로 전달되는 것은 프로세스 목록(전체)이 아니라 한번에 하나씩 들어오게 됩니다.

EnumWindowsProc에서는 첫번째 매개변수로 들어온 윈도우 핸들을 이용하여 GetWindowLong을 호출해 윈도우의 스타일을 얻어오고, 해당윈도우의 캡션이 존재하는지 확인합니다.

그 다음으로 부모가 바탕화면인지 확인하여, 이것이 최상위의 윈도우인지 확인하게 됩니다. GetWindowText함수를 이용하여, 윈도우(응용프로그램)의 이름을 얻어온후, GetClassLong을 사용하여 해당 윈도우의 HICON 아이콘핸들을 얻어오게 됩니다(.net에서 사용하기 위해 IntPtr 사용), 이렇게 얻어온 아이콘핸들을 이용하여 .net의 Icon 클래스의 static메소드인 FromHandle을 이용하여 Icon 객체를 생성합니다.

예외처리 구문은 자기 자신의 윈도우일 경우 Icon을 못얻어오는 경우가 생겨서, try를 사용하였습니다.
EnumWindowsProc의 return은 true로 해주셔야, 계속하여 호출하게 됩니다. 상황에따라 원하는 윈도우를 찾은다음에 false를 return하면 되겠습니다. 실행시켜 보니 잘되는군요!
사실, API의 EnumWindows를 사용하지 않고 .net의 Process 클래스를 사용해도 현재 사용중인 프로세스의 목록을 얻어, 바탕화면이 부모인지 확인하고 MainWindowTitle을 확인하여 이와같은 기능을 만들 수 도 있습니다.(하지만 탐색기 같은 일부창의 경우 윈도우의 이름을 얻어올 수 없는 문제가 있습니다.) 아래는 Process를 이용하는 간단한 예제입니다.
Process[] pro = Process.GetProcesses();            
for (int i = 0; i < pro.Length; i++)
{
     if (pro[i].MainWindowHandle != IntPtr.Zero)
     {
          if (pro[i].MainWindowTitle == "")
              continue;
       //To do..
     }
}
Posted by 맨날맑음
,

안녕하세요? 맨날맑음 입니다.

네이트온을 사용하다 보면 친구가 로그인 했더나 대화 요청이 들어올때 트레이 아이콘 주위에 알림창이 뜨는것을 볼 수  있습니다.

이와 같이 트레이창의 위치를 윈도우 주위로 위치하는 것은 .NET에서도 Win32API의 SystemParamete
rsInfo
를 사용하면 간단하게 구현 할 수 있습니다.

.NET에서 Win32API를 사용하기위해 우선 using System.Runtime.InteropServices; 이 필요합니다.
또한 SystemParametersInfo에 대한 아래와 같은 선언도 필요합니다.
 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int SystemParametersInfo(int uAction, int uParam, out RECT lpvParam, int fuWinIni);


SystemParametersInfo 함수를 호출하여 작업표시줄의 영역을 뺀 화면의 크기를 얻어 오기 위하여 RECT 구조체가 필요 하기 때문에 아래와 같이 임의로 RECT 구조체를 정의해 줍니다.
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
        public int left;
        public int top;
        public int right;
        public int bottom;
}
사실 SystemParametersInfo는 매우 다양한 기능을 수행하는 API입니다. 여러가지 시스템 정보를 설정하거나 가져 올 수 있습니다. 예를들어 바탕화면 이미지를 변경한다던지, 작업영역의 크기를 알아낼 수 도 있고, 화면보호기의 동작 유무를 제어 할 수도 있습니다. 인자에 따른 기능이 너무 많아서 좀더 자세한 기능을 알고 싶다면 아래의 링크에서 MSDN 도움말을 확인 하시면 됩니다.
http://msdn.microsoft.com/en-us/library/ms724947(VS.85).aspx

위에 그림에서 보듯이 작업표시줄의 트레이 아이콘 위에 윈도우를 띄우려면(무언가 보여주고 싶다면...) 작업표시줄의 영역을 제외한 윈도우의 실질적인 작업영역의 크기를 얻어오면 됩니다.
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int SystemParametersInfo(int uAction, int uParam, out RECT lpvParam, int fuWinIni);

        private void SetFormLocationToTray(Form form)
        {
            int SPI_GETWORKAREA = 0x0030; //작업영역을 알아오는 Flag
            RECT r = new RECT();
            SystemParametersInfo(SPI_GETWORKAREA, 0, out r, 0);
            Size s = form.Size;
            Point p = new Point(r.right - s.Width, r.bottom - s.Height); 
            form.Location = p;
        }
 
SetFormLocationToTray 함수는 인자로 들어온 Form 객체의 위치를 Tray 주위로 옮겨 주는 역할을 합니다.
SystemParametersInfo의 첫번째 인자중 0x0030(SPI_GETWORKAREA)을 넣으면 작업표시중을 뺀 작업영역의 크기를 3번째 인자의 RECT 구조체에 얻어오게 됩니다.
알아온 작업영역의 크기로 위의 소스에서 보는것 처럼 현재 폼의 크기에 따라 계산을 하여 폼의 location을 변경하여 네이트온처럼 트레이 주위에 윈도우가 뜨게 할 수 있습니다.
Posted by 맨날맑음
,
안녕하세요? 맨날맑음 입니다.

어플리케이션을 개발 하다 보면 같은 프로그램이 두번 실행 되지 않아야 하는 경우가 종종 생깁니다.
이를 해결 하기 위해 여러 방법이 있지만.. 이번 포스팅에 소개 할 내용은 커널 동기화 객체 중 하나인 뮤텍스(Mutex)를 이용하여 해결하는 방법을 소개하려 합니다.

Mutex는 .NET의 System.Threding 네이스페이스에 포함된 클래스 입니다. 사실 둘이상의 스레드가 동시에 공유될 수 있는 리소스에 접근 할때 데드락의 위험이 있으므로 동기화 매커니즘이 필요하게 되는데요. Mutex는 리소스에 대한 단독 엑세스 권한을 하나의 스레드에만 부여하여 동기화하는 기본형식 입니다.
같은 리소스에 접근 할때 하나의 스레드가 Mutex를 걸고 어떠한 일을 수행 할 경우 다른 스레드는 먼저 수행하고 있는 스레드가 Mutex를 해제 할때까지 기다리게(일시중지) 되게 됩니다.
Mutex에 관한 자세한 설명은 아래의 링크에서 MSDN 도움말을 확인 할 수 있습니다.
http://msdn.microsoft.com/ko-kr/library/system.threading.mutex.mutex.aspx

테스트 하는 어플리케이션으로는 Windows Form 프로젝트를 예로 들겠습니다.
WinForm 프로젝트를 생성하면 Program.cs 가 생성 됩니다.
Main 함수에 다음과 같은 코드를 추가 시켜 주면 메세지 막스가 뜨면서 해당하는 프로그램이 중복 실행 되었다는걸 알릴 수 있습니다. 매우 간단하죠?
static class Program
    {
        /// 
        /// 해당 응용 프로그램의 주 진입점입니다.
        /// 
        [STAThread]
        static void Main()
        {
            bool createdNew;
            Mutex dup = new Mutex(true, "Focus Explorer Mutex", out createdNew); //Mutex생성
            if (createdNew)
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new MainForm());
                dup.ReleaseMutex(); //Mutex 해제
            }
            else // 프로그램이 중복 실행된 경우
            {
                MessageBox.Show("이미 프로그램이 실행중입니다.","Focus Explorer");
            }
        }
    }
 
우선 Mutex를 생성하고, 생성자에 두번째 인자에 뮤텍스 이름을 지정합니다.
세번째 인자로준 bool 값이 false일 경우 이미 뮤텍스가 걸리있는 상태이기 때문에.. 해당 프로젝트가 이미 생성되었다고 생각하고 프로그램이 실행중이라는 메세지 박스를 띄우고, 프로그램을 종료합니다.

이상 간단한 Mutex를 사용한 프로그램 중복 방지 방법이었습니다.
Posted by 맨날맑음
,
안녕하세요? 맨날맑음 입니다.

.NET 으로 프로젝트를 만들고, 배포를 하는 방법은 Click Once같은 방법도 있지만 이번에는 Windows Installer 배포에 대해 알아보겠습니다. 기존에 .Net으로 프로젝트 개발만 해보았지, 배포는 신경을 쓰지 않아 잘 모르다가, 이번에 VS2008에서 기본으로 제공되는 배포 프로젝트의 사용법을 알아 보았습니다.
아래 링크를 따라가면 배포프로젝트에 관한 MSDN 도움말을 보실 수 있습니다.
http://msdn.microsoft.com/ko-kr/library/206sadcd(VS.80).aspx

우선 배포할 프로그램을 준비합니다. 저는 간단한 윈폼 프로젝트로 하겠습니다.

VS2008에서 새 프로젝트를 추가 하고, 기타 프로젝트 형식 -> 설치 및 배포 -> 설치프로젝트를 선택합니다.

다음과 같이 배포 프로젝트가 생성됩니다.

솔루션 탐색기와 속성 창을 보면 아래와 같이 프로젝트와, 여러 속성이 보이게 됩니다.
이제 이 속성들의 의미를 하나하나 알아보겠습니다.
▷AddRemoveProgramIcon : 제어판 -> 프로그램 추가/제거에 표시될 아이콘을 등록합니다.
▷Author : 프로젝트 작성자의 이름을 등록합니다.
▷Desciption : 설치 관리자에 관한 설명을 등록합니다.
▷DetectNewerInstalledVersion : 프로그램 설치시 버전 비교를 통해 새 버전인지 확인 해 줍니다. 이미 설치 되있을 경우 설치 되어있다고 알려주기도 합니다.
▷Keyword : 설치 관리자를 검색하는데 사용 할 키워드를 지정합니다.
▷Localization : 로케일을 적용한다고 하는데, 글로벌 프로그램이 아니라면 신경 안써도 될 듯 합니다.
▷Manufacturer : 제조업체의 이름을 지정합니다
▷MunufacturerUrl : 제조업체의 홈페이지 링크를 지정.
▷PostBuildEvent : 배포프로젝트를 빌드한 후에 실행 할 명령줄을 지정합니다.
▷PreBuildEvent : 배포프로젝트를 빌드하기 전에 실행 할 명령줄을 지정합니다.
▷ProductCode : 응용프로그램의 고유 식별자(GUID)를 지정합니다.
▷ProductName : 프로그램의 공개 이름을 지정합니다.
▷RemovePrevionsVersions : 설치시 이전버전을 삭제 할지를 지정합니다.
▷RunPostBuildEvent : PostBuildEvent 속성에서 지정된 명령줄을 실행할 시기를 결정합니다.
▷SearchPath : 개발 컴퓨터의 어셈블리, 파일 또는 병합 모듈을 검색하는 데 사용되는 경로를 지정합니다.
Subject : 프로그램을 설명하는 추가정보를 지정합니다.
▷SupportPhone : 전화번호를 지정합니다.
▷SupportUrl : 마찬가지로 추가 설명을 하는 웹사이트 주소
TargetPlatform : 프로그램이 실행될 플랫폼을 지정.
▷Title : 설치 관리자의 제목을 지정합니다.
▷UpgradeCode : 프로그램 버전이 여러가지 일때 고유식별자를 지정합니다.
▷Virsion : 버전을 지정합니다.




속성 참 많네요.. 정작 중요한건 ProductName, Title, Author, MAnufacturer 정도 입니다.

이제 파일시스템 탭으로 이동하여 대상 컴퓨터의 파일 시스템 -> 응용 프로그램 폴더를 선택한 후 속성창을 확인합니다.

DefalutLocation이라는 속성에 [ProgramFilesFolder][Manufacturer]\[ProductName] 라고 되어 있습니다. 인스톨시 프로그램이 설치될 폴더 인데요. 이 설정으로 하게 되면 프로그램파일 폴더 밑에 제조회사명 밑에 프로그램 이름 폴더 안에 깔리게 되겠습니다. 맘에 드는 폴더로 변경 하시면 됩니다.

이제 파일을 추가 해 보겠습니다. 응용 프로그램 폴더에서 마우스 오른쪽 버튼을 누르면 파일이나 폴더등을 추가 할 수 있습니다. 미리 만들어 놓은 샘플 어플리케이션을 추가 하겠습니다.


이렇게 하면 앞서 지정된 설치 폴더에 MainApp.exe가 설치 됩니다. 명색이 인스톨 프로그램인데 이것만 지정하면 안되겠죠? 위에 사용자 바탕화면사용자 프로그램 메뉴가 보입니다.
말 그래로 사용자 바탕화면은 바탕화면에 설치 될 파일을 지정 할 수 있고, 사용자 프로그램 메뉴는 [시작]->[프로그램]의 폴더나 파일을 지정 할 수 있습니다.
바탕화면과 프로그램 메뉴에 MainApp.exe의 바로가기를 넣어주면 클라이언트가 아주 편리 할 것 같습니다.
MainApp.exe를 마우스 오른쪽 버튼으로 클릭하여 바로가기를 만듭니다. 저는 두개를 만들어 이름을 원하는데로 변경한 후 하나는 사용자 바탕화면으로 끌어다 놓았고, 사용자 프로그램 메뉴에는 [새폴더]를 하나 추가 하여, 그 안에 넣었습니다. 그리고 아이콘 파일(.ico)도 하나 추가하여 바로가기의 속성중 Icon에 연결 시켜 줍니다. 그럼 바로가기가 우리가 지정한 아이콘으로 생성 됩니다.

이제 언인스톨 기능을 하는 바로가기도 지원해 주어야 좀 더 있어보일 것입니다.
바탕화면에서 텍스트문서(txt)를 하나 추가해서 확장자를 bat로 바꾸어 줍니다. 편집기로 파일을 열어 Msiexec /x {ProductCode} 를 추가 해 줍니다. 여기서 ProductCode는 위에 프로젝트 속성중에 있던 코드 입니다. 지금 예제 대로 하면 Msiexec /x {A1715BBB-A953-4F01-B788-168542ED2BC3} 이 되겠네요.

현재 배포하는 샘플 프로그램이 매우 간단하여 파일이 하나이지만, 대부분의 응용은 여러 DLL을 포함하고 있을것입니다. 그리고 각 파일마다 설치하고 싶은 경로가 다를 수 있는데, 파일시스템 탭의 대상 컴퓨터의 파일 시스템을 오른쪽 버튼으로 누르면 특수폴더 추가에서 원하는 폴더를 추가 할 수 있습니다.

이제 이 파일을 응용프로그램 폴더에 포함 시켜주고, 마찬가지 방법으로 바로 가기를 만들어서 원하는 곳에 추가 시켜 줍니다. 프로젝트를 다시 빌드 하고 테스트 해보겠습니다. 프로젝트 폴더의 Relese 폴더에 들어가니 파일이 두개 보입니다.(Relese모드로 빌드 했을 경우, Debug 모드 일경우 Debug 폴더에 생성)
Setup.exe를 더블 클릭하니 설치가 잘 됩니다.
설치시 생긴 바로가기 아이콘으로 Uninstall도 잘되는지 확인해 보겠습니다.
정상적으로 잘 되는걸 볼 수 있습니다. 쓰다 보니 스크롤의 압박이군요;
만약 설치 대상컴퓨터(클라이언트)에 .Net Framwork가 없을때 자동으로 설치되게 하는 것 까지 쓰려고 했는데,
다음편으로 넘겨야 할 것 같습니다. 다음편에는 이같은 기능을 해주는 Boot Strapper에 관해 포스팅 하겠습니다.
Posted by 맨날맑음
,
안녕하세요? 맨날맑음 입니다.

WPF나 Silverlight 와는 다르게 Windows Form에는 '로드 완료' 이벤트가 없습니다. Load 이벤트가 제공 되고 있기는 하지만 MSDN에 나온 의미는 '폼이 처음으로 표시되기 전에 발생합니다.' 입니다. 즉 로드 완료가 아니라는 것입니다.

이것이 왜 문제일까?
로드 완료가 아닌 로드중 이벤트만 제공하여 생기는 문제가 있습니다.
예를 들어 프로그램을 시작할 때 폼을 숨기려고 한다고 하면, Hide() 메서드를 호출 해야합니다.
실행 할때 숨기려 했으니 당연히 폼의 생성자에 Hide()를 넣어보지만 전혀 동작 하지 않습니다.
두번째 방법으로 폼의 Load 이벤트 핸들러 안에 Hide()를 넣어봅니다. 역시 동작하지 않네요;
정확하지 는 않지만, MSDN의 설명대로 폼이 아직 표시되기 전이라 Hide()가 먹히지 않는 것 같습니다.
이밖에 Focus()나 몇몇 다른 메서드도 동작하지 않습니다.

어떻게 해결할까?
위의 문제를 해결하려면 '로드완료' 이벤트를 찾아야 하는데 아무리 뒤져보아도 로드완료는 존재하지 않았습니다.
그래서 임시 방편으로 Application.Idle 이벤트를 사용하여 해결 하였습니다. Application.Idle 이벤트는 말그대로 프로그램이 아이들 상태(쉬고 있을때) 발생합니다. 이걸 응용하면, 생성자에서 이벤트를 걸어주고, 이벤트 핸들러에서 이벤트를 빼주는 방법으로 로드 완료된 시점을 잡을 수 있습니다. 물론 엄밀히 말하면 프로그램이 더이상 할 일이 없어 쉬고 있을 시점을 잡아내는 것입니다.

using System;

using System.Windows.Forms;

namespace Sample

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

            //Idle이벤트를 걸어준다.

            Application.Idle += Application_Idle;

        }

        void Application_Idle(object sender, EventArgs e)

        {

            //Idle이벤트를 없앤다.

            Application.Idle -= Application_Idle;

            this.Hide();

        }

    }

}


위의 코드에서 Idle 이벤트 핸들러에서 Application.Idle -= Application_Idle; 이벤트를 빼주는 방법으로 프로그램이 처음으로 쉴때(로드완료)를 잡아내는 것입니다. 이렇게 임시 방편이나마 로드 완료 시점을 잡아서 hide() 메서드를 동작시킬 수 있습니다.
Posted by 맨날맑음
,

안녕하세요? 맨날맑음 입니다.

네이트을 보면 X(종료) 버튼을 누를시 프로그램이 꺼지지 않고, 트레이로 가게 됩니다.
이번에 알아볼 내용은 .net Windows Forms 프로젝트에서 이런 기능을 구현 하는 것입니다.

우선 준비물이 필요합니다. 트레이에 나타나게 할 아이콘 파일(.ico)입니다.
아이콘 파일은 웹상에서 마음에 드는것을 다운로드 받아도 되고, 포토샵이나 기타 프로그램으로
그림파일을 변환시켜 사용하셔도 됩니다. 물론 솔루션 탐색기 -> 해당 프로젝트(우클릭) -> 추가 -> 새항목 -> 아이콘파일을 선택하셔서 직접 그려서 사용하셔도 됩니다. 각자 취향에 맞는 아이콘을 준비해 주면 됩니다.

우선 Windows Forms 프로젝트를 생성합니다.

도구상자에서 NotifyIcon을 선택하여 Form에 추가 합니다. 추가 후 소성창의 Icon 속성에 준비해준 Ico 파일을 연결해 줍니다. 여기까지만 하고 실행시켜도 트레이에 우리 프로그램의 아이콘이 보이는 걸 볼 수 있습니다.

트레이에서 마우스 오른쪽 버튼을 눌렀을 경우 나올 메뉴를 위해 ContextMenuStrip도 추가해 줍니다.
ContextMenuStrip의 메뉴를 추가 합니다. 간단히 '종료' 메뉴(Name : ExitToolStripMenuItem)만 넣었습니다. 다시 notifyIcon1의 속성창으로 가서 ContextMenuStrip의 값을 contextMenuStrip1로 지정 합니다.
여기까지 하셨으면 프로그램 실행 시 트레이에 아이콘이 나타나게 되고, 오른쪽 버튼으로 아이콘을 클릭하면
종료 메뉴가 나타나게 됩니다.

이제 코드창(cs)으로 가서 몇가지 이벤트핸들러만 구현해 주면 트레이에 아이콘이 나타나는 프로그램을 만들수 있습니다.
using System;
using System.Windows.Forms;
namespace TraySample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.FormClosing += Form1_FormClosing;
            this.notifyIcon1.DoubleClick += notifyIcon1_DoubleClick;
            this.ExitToolStripMenuItem.Click += ExitToolStripMenuItem_Click;
        }
        // 트레이의 종료 메뉴를 눌렀을때
        void ExitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //트레이아이콘 없앰
            notifyIcon1.Visible = false;
            //프로세스 종료
            Application.Exit();
        }
        //트레이 아이콘을 더블클릭 했을시 호출
        void notifyIcon1_DoubleClick(object sender, EventArgs e)
        {
            this.Visible = true; // 폼의 표시
            if (this.WindowState == FormWindowState.Minimized)
                this.WindowState = FormWindowState.Normal; // 최소화를 멈춘다 
            this.Activate(); // 폼을 활성화 시킨다
        }
        //폼이 종료 되려 할때 호출
        void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = true; // 종료 이벤트를 취소 시킨다
            this.Visible = false; // 폼을 표시하지 않는다;
        }
    }
}
주석으로 충분한 설명이 있지만 간단히 소스를 설명하면, 우선 타이틀바의 X 단추를 눌렀을때 프로그램이 종료 되는것을 막기위해 FormClosing 이벤트 핸들러에서 폼이 종료 이벤트를 취소 시켰습니다.
또한 트레이 아이콘을 더블클릭하면 폼이 다시 나와야함으로 notifyIcon1_DoubleClick 이벤트 핸들러에서 는 폼을 다시 표시하고, 활성화 시켰으며, 메뉴에서 종료를 선택했을때는 프로그램을 종료하게 하였습니다.

이렇게 간단하게 네이트온처럼 트레이에 표시되는 응용프로그램을 만들 수 있습니다.
Posted by 맨날맑음
,
안녕하세요? 맨날맑음 입니다.
아르바이트 삼아 하고있는 프로그램 제작하다가 타이틀바의 높이를 알아 올 일이 생겼습니다.
C#에서는 제공하는 클래스가 없는것 같고, API 함수를 쓰면 쉽게 해결 할 수 있습니다.
int WINAPI GetSystemMetrics(int nIndex );
사실 GetSystemMetrics 함수는 매개변수에 따라 여러가지 기능을 하는데요. 주로 시스템의 구성이나 설정을
얻어올 때 사용됩니다. 시스템 영역의 정보를 얻어오려면 이 함수의 기능을 찾아보면 거의 있습니다.
그중에서 이번엔 타이틀바의 높이를 알아오는 방법을 알아보겠습니다. 타이틀바의 높이는
SM_CYCAPTION
플래그를 매개변수로 넣어주어 리턴되는 값으로 얻어 올 수 있습니다.

저는 C#에서 사용하여야 했기 때문에 윈폼 프로젝트로 테스트해 보았습니다.
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern int GetSystemMetrics(int nIndex);
public Form1()
{
     InitializeComponent();
     const int SM_CYCAPTION = 4;
     int height = GetSystemMetrics(SM_CYCAPTION);            
     this.Text += height.ToString();
}
너무 간단하지만, C#에서 함수 사용을 위해 DLL 선언을 해주었고, CM_CYCAPTION = 4 를
매개변수로 하여 해당 함수를 호출하여 높이를 얻어왔습니다.


실행화면
윈도우 설정에서 타이틀바의 높이를 변경해 보았는데요. 잘 얻어오는 것을 알 수 있습니다.


Posted by 맨날맑음
,
프로젝트 도중 키 이벤트를 강제로 발생시켜야 하는 상황이 생겼는데요;;
SendMessage로 WM_KEYDOWN을 보내놓고,  키가 입력되지 않는다고 화내고 있었습니다''
keybd_event라는 키보드 메세지를 생성해주는 훌륭한 Win32 API함수가 있더군요

keybd_event 함수의 원형은 다음과 같습니다.(USER32.DLL)
VOID keybd_event(      
    BYTE bVk,              // 가상 키코드
    BYTE bScan,          // 하드웨어 스캔 코드
    DWORD dwFlags,     // 동작 지정 Flag
    PTR dwExtraInfo     // 추가 정보
);
bVK
          가상키코드를 지정하는데 1~125 범위의 값이어야 합니다.
bScan
         
사용하지 않는 인자 입니다.

dwFlags
          함수의 동작을 지정하는데 0을 넣을 경우 KeyDown이고 0x0002를 넣을 경우 KeyUP 입니다.

dwExtraInfo
          키스트로크 관련 32비트의 추가 정보를 지정 한다고 합니다.

C#에서 사용시!
[DllImport("user32.dll)]
public static extern void Keybd_event(byte vk, byte scan, int flags, ref int extrainfo);


※ ex)
const byte AltKey = 18;
const int KEYUP = 0x0002;   
int
Info=0;
keybd_event(AltKey, 0, 0, ref Info);   // ALT key 다운
keybd_event(AltKey, 0, KEYUP, ref Info);  // ALT key 업

Posted by 맨날맑음
,

정말 오랜만의 포스팅입니다. (이제부터 예의바른 블로거가 되기위해 존댓말로 포스팅하기로 했습니다)
핑계없는 무덤은 없다지만 핑계를 대자면,, 지금 진행하는 프로젝트에 쫒겨 시간이 없었다고,,라고 하고싶지만
게을러서 입니다. 게으른 블로거되서는 안되는데 큰일입니다;;

아무튼 이번 포스트에서는 FindName(); 메서드에 대해 알아보려 합니다.
여러분은 Silverlight App를 개발하면서 FindName()을 사용하십니까?
사실 일찌감치 FindName() 메서드의 존재와 역할을 알았지만 필요없는 메소드라 생각하여 잊어버렸습니다.
사용하지 않았죠...;

FindName();
System.Windows.Documents 네임스페이스의 Inline 클래스의 멤버매서드
역  할 : 개체의 x:Name 또는 Name 특성 값을 참조하여 Silverlight 개체를 가져온다.
인자값 : string
리턴형 : object


Page에 Rectangle이 있고 x:Name이 MyRect라고 할때  Page.xaml.cs에서는 MyRect라는 x:Name만으로도
Rectangle객체의 컨트롤이 가능합니다.
 예를들어 이런것이죠  >>   this.MyRect.Width = 100;
x:Name만 알아도 충분히 컨트롤이 가능한데 굳이 아래와 같이 복잡하게 객체를 알아올 필요성을 못느낀것이죠.
object o = this.FindName("MyRect");
Rectangle myrect = o as Rectangle;
if (myrect != null)
{
     // to do..
 }


그럼 FindName을 어떤때 써야될까요? 저는 이런 경우에 활용해 보았습니다.
를들어 보자면 로그인하는 시나리오가 적당하겠네요.

Page에서 로그인 버튼을 누르면 로그인 컨트롤(UserControl로 만들어진)이 새로 뜨고 사용자는 ID와 PW를 입력하여 로그인 하게 됩니다. 로그인 된다면 Page의 TextBlock에 사용자가 입력한 ID가 나타나게 한다고 합시다.

Page는 로그인 컨트롤을 알고 있지만(로그인 컨트롤의 Public한 속성에 접근할수 있지만) 로그인 컨트롤은
Page를 알지 못합니다. 물론 로그인컨트롤의 비하인드코드에서 this.parent를 이용하여 부모는 알수있을 겁니다.
하지만 (Page)this.parent;처럼 Page클래스로 캐스팅하면 컴파일 타임에 오류를 일으킵니다. 캐스팅 할수가 없죠.

저는 이걸 해결하기 위해 로그인 컨트롤의 생성자를 오버로딩하여 Page의 TextBlock 컨트롤을 인자로 넘겨주는 방법으로 코딩해왔습니다.

하지만 FindName을 이용하여 해결 할 수도 있습니다. this.parent를 찾아서 Panel로 캐스팅후
FindName() 메서드를 호출하여 TextBlock 개체를 찾으면 되는것이죠!
굳이 생성자에 넘기고 자시고 할 필요가 없습니다.

자.. 방법을 알았으니 간단한 스텁 프로그램을 작성해 보도록 하겠습니다.(앞에서 말한 로그인 예제로 하죠)

1. 우선 실버라이트 응용프로그램을 생성하구요 (프로젝트명을 FindNameTesting으로 했습니다.)
2. Page.xaml블랜드에서 열어 디자인 합니다. 간단하게 TextBlock(x:Name = "txtUserID")
   버튼(x:Name = "btnLog")을 하나 넣었습니다.
   버튼은 Grid로 만들었구요.. Grid안에 Rectangle 객체와 TextBlock(x:Name = "txtLogMSG")를 넣었습니다.


2. Silverlight 사용자정의 컨트롤(UserControl)을 추가 합니다.(LogInControl이라 명명 했습니다.)
    LogInControl을 블랜드에서 열어 디자인해 줍니다. 저는 아래와 같이 했구요;

  그냥 간단하게 ID를 입력받는 TextBox(x:Name = "txtInputID") 와  PW를 입력받는 PasswordBox(x:Name = "txtInputPW")를 추가하고 버튼(x:Name = "btnLogIn")도 하나 놓습니다.

3. 이제 LogInControl의 비하인드 코드(LogInControl.xaml.cs)로 가서 이벤트 처리 메서드를 작성합니다.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace FindNameTesting
{
    public partial class LogInControl : UserControl
    {
        public LogInControl()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(LogInControl_Loaded);
        }

        void LogInControl_Loaded(object sender, RoutedEventArgs e)
        {
            btnLogIn.MouseLeftButtonDown += new MouseButtonEventHandler(btnLogIn_MouseLeftButtonDown);
        }

        void btnLogIn_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!CheckedIdAndPw())            
            {
                Storyboard1.Begin();
                return;
            }
            object o = ((Panel)this.Parent).FindName("txtUserID");
            TextBlock txtuserid = o as TextBlock;
            if (txtuserid != null)
            {
                txtuserid.Text = string.Format("{0}님 환영합니다^^",txtInputID.Text);
            }

            object o2 = ((Panel)this.Parent).FindName("txtLogMSG");
            TextBlock txtlogmsg = o2 as TextBlock;
            if (txtlogmsg != null)
            {
                txtlogmsg.Text = "LogOut";
            }
            ((Panel)this.Parent).Children.Remove(this);            
        }

        private bool CheckedIdAndPw()
        {
            if (txtInputID.Text != "맨날맑음" || txtInputPW.Password != "silverlight")
            {
                return false;
            }
            return true;
        }
    }
}

    로그인 버튼을 누르면 LoginControl의 부모인 Page에서 txtUserID개체를 찾아 입력받은 ID를 대입했구요
    같은방법으로 Page에 있는 로그버튼의 TextBlock을 찾아 LogIn이라는 글자도 LogOut으로 변경하였습니다.
    그후에 생성된 LoginControl을 Page에서 제거 하였습니다.

4. 마지막으로 Page.xaml.cs를 작성합니다.
using System.Windows.Controls;
using System.Windows.Input;

namespace FindNameTesting
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
            this.btnLog.MouseLeftButtonDown += new MouseButtonEventHandler(btnLog_MouseLeftButtonDown);
        }

        void btnLog_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (txtLogMSG.Text == "LogIn")
            {
                this.LayoutRoot.Children.Add(new LogInControl());
            }
            else
            {
                txtLogMSG.Text = "LogIn";
                txtUserID.Text = "안녕하세요? 로그인하세요";
            }
        }
    }
}

매우 간단합니다. 버튼을 누르게 되면 로그인컨트롤(LogINControl)을 생성합니다자..이제 로그인하게되면
로그인한 화면이 Page의 TextBox를 갱신시킬까요?... 네~잘됩니다
아래는 지금 만든 예제를 조금 수정한 것입니다~(ID : 맨날맑음, PW : silverlight) 테스트 해보시면 잘되는걸 볼 수있습니다.



이외에도 WPF에서는 FindName() 메서드로 개체를 불러와야만 개체의 컨트롤이 가능합니다~
이 방법을 사용하면 부모든 부모의 부모든 상관없이 type만 알고있다면 모든 컨트롤을 제어할수 있겠고,
public한 속성들도 제어할 수 있습니다!
다른 쓰임새가 많겠지만 아직 실력이 미천하여 이럴때 사용하고 있습니다;
다른곳에 활용하신분들은 피드백이나 댓글 부탁드립니다.
Posted by 맨날맑음
,
티스토리 블로그에 실버라이트 올리기!

실버라이트를 빌드하면  Debug 폴더나 ClientBin 폴더내에 xap 파일이 생성된다;
정확히는 모르겠으나 여기저기 웹서핑과 MSDN 참조 결과 xap 파일만 있으면 실버라이트 결과물을
HTML에 <Object> 태그로 집어 넣을 수 있겠다 싶어서 티스토리에 올릴려고 무진장 많이 노력했지만
계속 흰화면만 뜨는것이었다;
<object  data="data:application/x-silverlight-2," type="application/x-silverlight-2">
    <param name="source" value="xap 파일의 경로"/>
  
    <!-- Display installation image. -->
    <a href="
http://go.microsoft.com/fwlink/?LinkID=124807"
        style="text-decoration: none;">
        <img src="
http://go.microsoft.com/fwlink/?LinkId=108181"
            alt="Get Microsoft Silverlight"
            style="border-style: none"/>
    </a>
</object>  

주석아래 <a href>와 <img> 태그는 실버라이트 런타임이 설치되지 않았을때 나올 다운로드 이미지이다!
HTML의 <BODY> 태그내에 저 구문을 넣어주면 된다고 MSDN에는 나와있는데;;
이게 로컬에 xap 파일이 있는경우에는 잘되지만 다른곳에 있으면 잘안되서 고민한 끝에;

약간 편법으로 저 HTML 파일과 xap 파일을 웹서버에 함게 업로드하고
HTML 문법인 IFRAME을 사용하여 저 HTML을 띄우는 방법으로 했더니만 아주 잘됬다!!
<IFRAME  src="HTML의 경로">
</IFRAME>

성공(?)기념 !! Silverlight DeepZoom Sample!!(사실 LiveDK 블로그에 있는 딥줌이 부러워서'')
 마우스로 클릭하거나 휠을 돌려보세요


PS. 먼가 다른 방법을 아시는분은 댓글이나 피드백을... 부탁드려요'' 굽신굽신

Posted by 맨날맑음
,