개발팁2019. 3. 28. 14:41

ToggleButton 은 버튼의 눌림을 표현 하는 방법으로,

한번 누르면 눌림상태로, 다시 누르면, 튀어 나온 상태를 말한다.


[Winform]

 

System.Windows.Forms 에는 별도의 ToggleButton 이 없고, 일반 Button 은 ToggleButton 처럼 사용할 수 없다.

 

System.Windows.Forms 의 CheckBox 나 RadioButton 의 Appearance 속성을 이용하여, ToggleButton 으로 사용할 수 있다.

 

일단, Panel 위에 CheckBox 나 RadioButton 을 올려놓는다.

 

한 Container 위에서, CheckBox 는 여러개 선택이 가능하고, RadioButton 은 단 1개만 선택이 가능하다.

 

 

CheckBox 나 RadioButton Control의 속성을 보면,

Appearance 의 기본값 Normal 을 Button 으로 바꿔준다.

 

 

이제 버튼의 모양도 동일하게 보여지게 된다.

 

 

System.Windows.Forms
CheckBox

버튼의 눌림은 .Checked = true, 눌림해제는 .Checked = false 로 체크박스나 라디오버튼으로 그대로 사용하며,

화면에 보여지는 형식만 다르다는것을 알 수 있다.

 

 

[WPF]

 

WPF 에는 ToggleButton 컨트롤이 있어서, 바로 사용이 가능하다.

 

<ToggleButton Height="25" Content="Level 1" IsChecked="{Binding level1_checked, UpdateSourceTrigger=PropertyChanged}" Checked="ShowScore"></ToggleButton>

 

Winform 에서 Checkbox 로 토글버튼을 만든것과 동일한 결과를 얻을수 있다.

 

추가적으로, RadioButton 으로 컨테이너 또는 그룹 안에서 하나의 토글 버튼만 선택이 가능하게 하기 위해서는,

Winform 처럼 RadioButton 으로 구현하면 된다.

 

WPF 의 RadioButton 컨트롤의 Style 속성을 지정하여, 토글버튼으로 보여지게 된다.

 

Style="{StaticResource {x:Type ToggleButton}}"

 

<RadioButton Height="25" Content="Level 1" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="{Binding level1_checked, UpdateSourceTrigger=PropertyChanged}"></RadioButton>
<RadioButton Height="25" Content="Level 2" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="{Binding level2_checked, UpdateSourceTrigger=PropertyChanged}"></RadioButton>
<RadioButton Height="25" Content="Level 3" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="{Binding level3_checked, UpdateSourceTrigger=PropertyChanged}"></RadioButton>
<RadioButton Height="25" Content="Level 4" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="{Binding level4_checked, UpdateSourceTrigger=PropertyChanged}"></RadioButton>
<RadioButton Height="25" Content="Level 5" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="{Binding level5_checked, UpdateSourceTrigger=PropertyChanged}"></RadioButton>

 

WPF RadioButton 을 이용한 토글버튼

 

Posted by 헝개
개발팁2019. 3. 19. 14:42

[C#] 파일시스템의 폴더감시 FileSystemWatcher

 

System.IO.FileSystemWatcher

 

System.IO 에 속한, FileSystemWatcher 를 이용하면, 폴더내의 파일시스템의 변화를 감지할 수 있다.

 

여기서 말하는 파일 시스템은, 파일과 폴더를 모두 포함한다.

 

감지할 수 있는 이벤트는

 

Changed - 파일이나 폴더의 변경
Created - 파일이나 폴더의 생성
Deleted - 파일이나 폴더의 삭제
Renamed - 파일이나 폴더의 이름 변경

 


실제 코드는 아래와 같이 구성하면 된다.

 

 

private void Run_Watcher()
{
 this.m_FolderWatcher = new FileSystemWatcher();

 this.m_FolderWatcher.Filter = "*.*";
 this.m_FolderWatcher.Path = "C:\\Program Files\\";
 this.m_FolderWatcher.IncludeSubdirectories = true;

 this.m_FolderWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
 this.m_FolderWatcher.Changed += new FileSystemEventHandler(Watcher_OnChanged);
 this.m_FolderWatcher.Created += new FileSystemEventHandler(Watcher_OnChanged);
 this.m_FolderWatcher.Deleted += new FileSystemEventHandler(Watcher_OnChanged);
 this.m_FolderWatcher.Renamed += new RenamedEventHandler(Watcher_OnRenamed);
 this.m_FolderWatcher.EnableRaisingEvents = true;
}

 

private void Watcher_OnChanged(object sender, FileSystemEventArgs e)
{
 Console.WriteLine(string.Format("{0} {1}", e.FullPath, e.ChangeType.ToString()));
}

 

private void Watcher_OnRenamed(object sender, RenamedEventArgs e)
{
 Console.WriteLine(string.Format("{0} {1} to {2}", e.OldFullPath, e.ChangeType.ToString(), e.Name));
}

 

 

 

Filter 라는것은, 지정된 경로(Path) 아래에서도, 모든 파일(*.*) 을 다 감시 할지, 특정 파일(ex : *.txt) 만 감시할지를 지정하게 된다.

 

IncludeSubdirectories 는 경로(Path) 아래의 모든 하위폴더를 다 포함할지 여부이다.

 

NotifyFilter 는 파일이나 폴더의 변경형식을 선택할 수 있다.

 

다수의 형식을 선택하기 위해 bitwise or 연산자를 사용한다.

8가지중에 선택할 수 있다.

 

FileName = 1,
DirectoryName = 2,
Attributes = 4,
Size = 8,
LastWrite = 16,
LastAccess = 32,
CreationTime = 64,
Security = 256

 

 

Changed, Created, Deleted 는 모두 event 파라미터가 동일(FileSystemEventArgs) 하기 때문에, 하나의 이벤트 핸들러로 연결시켰다.
(각자 별도의 이벤트 핸들러를 만들어도 상관은 없다.)


FileSystemEventArgs 의 ChangeType 으로, 어떤 변화인지 찾아 낼 수 있다. (Created, Changed, Deleted, Renamed)

 

마지막으로,
EnableRaisingEvents = true

 

를 해줌으로써, 파일시스템의 감시가 활성화 된다.

 

감시를 중단하고 싶을때는,

EnableRaisingEvents = false

 

로 해주면 된다.

실제로, 특정 폴더를 감시하는 프로그램은 아래와 같이 나오게 된다.

 

 

 

 

한가지 팁으로,

 

특정 FTP 경로를 감시하고 있다가, FTP 로 업로드가 될 경우, 자동으로 배포 하는 프로그램을 만든다면,

어떤 이벤트를 사용해야 할까?

Created? Changed?

 

정답은 2개를 다 사용해야 한다.

 

우선, FTP 로 파일이 업로드 시작되면, Created 이벤트가 발생한다.

그리고, 파일이 업로드가 진행되면, Changed 가 발생하게 된다.

 

여기서 중요한것은 FTP로 파일 업로드가 종료된(파일이 모두 업로드된) 시점을 알아야 한다.

이를 위해, 파일을 Write 모드로 Open 해서, Open 이 된다면, 정상적으로 업로드가 완료된것으로 보면 된다.

 

 
private void Watcher_OnCreated(object sender, FileSystemEventArgs e)
{
 string ext = Path.GetExtension(e.FullPath);

 this.m_Wacher_CreatedFileName = e.FullPath;
}

 

private void Watcher_OnChanged(object sender, FileSystemEventArgs e)
{
 if (e.ChangeType != WatcherChangeTypes.Changed)
  return;

 if (this.m_Wacher_CreatedFileName != e.FullPath)
  return;

 

 // 쓰기모드로 오픈이 가능해야, 파일복사가 완료된것으로 본다.
 try
 {
  FileStream fs = File.Open(e.FullPath, FileMode.Open);
  fs.Close();
  fs.Dispose();
 }
 catch(IOException)
 {
  return;
 }
}

 

 

위 코드는 실제로 사용되는 코드이다.

 

 

Posted by 헝개
개발팁2017. 12. 18. 13:10

C# 에서 파일을 복사하는 방법은

 

간단하게,

 

System.IO.File.Copy 메서드를 이용하는 방법이다.

 

System.IO.File.Copy( source_file_nm, target_file_nm, is_overwrite);

 

단순하면서도, 적절한 Exception 처리만 해준다면, 아주 강력하다.

 

하지만, 한줄의 명령어로, Synchronous 작업이기 때문에, 파일이 복사 되는 동안에는 아무것도 하지 못한다.

 

파일이 복사되는 동안에, 진행률을 표시하기 위해서는, 위의 파일 복사 방법이 아닌, 비동기 작업 또는 쓰레드 작업이 필요하다.

 

파일이 복사 되는 동안에, 진행률도 표시해야 하고, 프로그레스바도 움직여줘야 하기 때문이다.

 

위의 방법은 한번에 파일복사 명령을 주고, 진행이 끝나면, 알려주는 방법이라면,

 

아래의 방법은, 우리가 직접 파일 복사를 주도하게 될것이다.

 

복사할 source_file_nm 의 경로에서 파일을 조금씩(buffer) 읽어서,  target_file_nm 에다가 write 해주는 방법이며,

 

이것은, loop 를 통해서, 파일의 내용을 모두 복사할때까지, 반복으로 작업을 진행하며,

 

진행하는 중간에 진행률과, 프로그레스바를 같이 움직여 줄것이다.

 

        byte[] buf = new byte[1024 * 512];  // 0.5M

 

        FileInfo file = new FileInfo(source_file_nm);

 

        if (!Directory.Exists(target_path))
                Directory.CreateDirectory(target_path);

 

        FileStream strIn = new FileStream(source_file_nm, FileMode.Open);
        FileStream strOut = new FileStream(Path.Combine(target_path, target_file_nm), FileMode.Create); 

 

 

버퍼를 0.5M 로 했으나, 1M로 해도 상관이 없다.

 

source 파일을 읽기용으로 Open 하고, target 파일을 쓰기용으로 Open 했다.

 

                while (strIn.Position < strIn.Length)
                {
                        int len = strIn.Read(buf, 0, buf.Length);
                        strOut.Write(buf, 0, len);
                }

 

                strOut.Flush();

                strIn.Close();
                strOut.Close();

 

source 를 버퍼로 (0.5M씩) 읽어서, target 파일에다 쓰기를 하고, 완료가 되면, 스트림을 모두 닫아준다.

 

이렇게 하면, 버퍼로 읽어서, 파일에 쓰는 작업은 완료가 되었다.

 

이제, 작업 중간에, 진행률과 프로그레스 바를 움직일 것이다.

 

진행률 표시를 위해 Label 을 하나 만들고, ProgressBar 를 하나 만들어 준다.

 

그리고, 아래와 같이 미리 설정을 해둔다.

 

progressBar.Maximum = Int32.MaxValue;


 

        byte[] buf = new byte[1024 * 512];  // 0.5M

        FileInfo file = new FileInfo(source_file_nm);

 

        if (!Directory.Exists(target_path))
                Directory.CreateDirectory(target_path);

 

        FileStream strIn = new FileStream(source_file_nm, FileMode.Open);
        FileStream strOut = new FileStream(Path.Combine(target_path, target_file_nm), FileMode.Create);

 

        try
        {
                while (strIn.Position < strIn.Length)
                {
                        int len = strIn.Read(buf, 0, buf.Length);
                        strOut.Write(buf, 0, len);

 

                        progressBar.Value = (int)(Int32.MaxValue * strIn.Position / strIn.Length);

                        this.lblBytes.Text = string.Format("{0:N0} / {1:N0}", strIn.Position, strIn.Length);
                }
        }
        catch (Exception ex)
        {
                MessageBox.Show("Error Occured : " + ex.Message, "Error");
                this.DialogResult = System.Windows.Forms.DialogResult.Abort;
        }
        finally
        {
                strOut.Flush();

                strIn.Close();
                strOut.Close();
        }

 

 

이렇게 버퍼로 복사하는 중간에 진행률과 프로그레스바를 표시하도록 수정했다.

그러나, 실제 파일이 복사되는 과정에서, 복사과 완료될동안, 응답없음 상태로 나왔다가, 파일 복사가 끝나야 결과가 나올 것이다.

 

위 작업을 쓰레드로 처리하거나, 백그라운드워커로 실행해줘야 한다.

 

BackgroundWorker 객체를 하나 만들고, 아래와 같이 이벤트 처리를 해준다.

 

this.bg_filecopy = new BackgroundWorker();
this.bg_filecopy.WorkerSupportsCancellation = true;
this.bg_filecopy.DoWork += new DoWorkEventHandler(bg_filecopy_DoWork);
this.bg_filecopy.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_filecopy_RunWorkerCompleted);

위에서 만들어준 코드는 bg_filecopy_DoWork 메서드 안에 넣어주면 된다.

 

이로써, 파일이 복사되는 중간에 상태표시가 되는것을 확인할 수 있다.

하지만, 뭔가 이상한 점이 있다.

 

위에서 만들어준 byte 버퍼를 사용하였지만, 실제 시스템에서는 또다른 버퍼가 있다는 것이다.

 

위에서 마지막 Flush 는 버퍼에 있는것을 모두 파일로 쓸 것이다.

하지만, Loop 안에 strOut.Write(buf, 0, len); 가 있지만, 시스템은 계속 버퍼에 쌓아두었다가 일정 시점 이후에 쓰기를 하는 것을 볼 수 있다.

 

따라서, Loop 안에서도 일정 시점마다  Flush 를 하도록 수정하는것이 좋다.

 

 

 

 

 

Posted by 헝개
개발팁2017. 11. 1. 14:39

C# WinForm 의 GroupBox 컨트롤의 title 영역에는 text 가 표시가 되지만,

 

이부분에 Checkbox 를 넣고, 체크박스가 선택된 경우에 GroupBox 가 활성화 되도록 하는 방법이다.

 

Form 안에 GroupBox 와 CheckBox 컨트를을 각각 1개씩 생성한다.

 

GroupBox 의 Text 값은 비워둔다.

CheckBox 의 Text 값은 적당한 값으로 적어준다.

 

CheckBox 의 위치는 아무곳에나 두어도 상관이 없다.

 

CheckBox Control 에서 마우스 오른쪽 버튼을 눌러서,

 

맨앞으로 가져오기 를 한번 클릭해준다.

 

그래야, GroupBox 에 CheckBox 가 감춰지지 않고 잘 보여지게 된다.

 

그리고, FORM 의 생성자를 아래와 같이 정의하자

 

public CustomForm()
{

    InitializeComponent();


    // 이부분을 추가한다.
    this.chkImageChange.Location = new Point(this.group_image.Location.X + 13, this.group_image.Location.Y - 1);
    this.chkVideoChange.Location = new Point(this.group_video.Location.X + 13, this.group_video.Location.Y - 1);

 

이부분은, CheckBox 컨트롤의 위치를 GroupBox컨트를의 title 영역위에 위치하도록 해주는 부분다.

 

이제 CheckBox 컨트롤의 이벤트를 추가할 것이다.

 

CheckedChanged 이벤트를 아래와 같이 정의한다.

 

private void chkImageChange_CheckedChanged(object sender, EventArgs e)
{
    this.group_image.Enabled = this.chkImageChange.Checked;

 

CheckBox 컨트롤을 체크하면, 그룹박스가 활성화되고, 해제되면, 그룹박스도 비활성화 될것이다.

 

이제, Form 에서 기본값으로 활성화 할지, 비활성화 할지를 선택하면 된다.

 


실행 화면 : 아래와 같은 형태로 나올 것이다.


 

 

 

Posted by 헝개
개발팁2017. 10. 28. 17:14

C1FlexGrid 를 새로 만들면, 기본적으로 1번째 column 이 회색으로 나온다.

이 부분을 fixed 라고 부르는데, 이 부분에 행번호(Line Number)를 표시하는 방법이다.

 

 

 

Caption : No 라고 적고,

DataType : Int32 로 변경한다.

Name : 은 비워둔다.

 

물론 Data Source 에 행번호를 넣어서 바인딩 할 수도 있지만, 이렇게 되면, Sort, Filtering 하게 되면, 행번호가 꼬이게 된다.

 

데이터로써가 아니라 항상 위에서 1부터 시작하는 행번호를 넣을 것이다.

 

 

 

 

그리드의 속성창에서 DrawMode 를 Normal -> OwnerDraw 로 변경한다.

 

 

 

이제, 그리드의 OwnerDrawCell 이벤트를 아래와 같이 정의한다.

 

private void FlexGrid_OwnerDrawCell(object sender, OwnerDrawCellEventArgs e)
{
 if ((e.Row >= this.Rows.Fixed) && (e.Col == (this.Cols.Fixed - 1)))
 {
     e.Text = ((e.Row - this.Rows.Fixed) + 1).ToString();
 }
}

 

이제, 빌드 해서 실행해보면 행번호가 잘 나올것이다.

 

 

[Sample]

 

 

Posted by 헝개
개발팁2016. 7. 26. 09:46

C# 어플리케이션, 보통 winform 형태의 어플리케이션에서
캐쉬를 만들고 처리하는 방법이다.

 

캐쉬는 데이터를 저장하고 꺼내쓰기 위해서 사용을 하는데,
데이터를 읽기 위해서 매번, 저장소에서 꺼내오는 것이 아니라,
저장소에서 읽어서 캐쉬에 저장을 해놓고, 캐쉬에서 꺼내쓴다면,
속도 향상과, 저장소에 대한 트래픽을 줄일수 있어 효율적으로 사용이 가능하다.

 

아래와 같은 방법으로 저장소에 대해서 자료를 가져오는 클래스를 정의하여 사용하면,
데이터를 가져다 쓰는 객체는 내부 구조에 상관없이 동일한 인터페이스로 사용할 수 있을것이다.


자료 클래스


- 자료읽기 요청
  1. 캐쉬에서 자료 읽기
     데이터가 없으면, 저장소에서 읽어옴
     캐쉬에 자료 저장
  2. 읽어온 자료 리턴

 

- 자료쓰기 요청
  1. 저장소에 자료 쓰기
  2. 캐쉬 삭제

 


저장소는 외부에서 변경되지 않는다는 보장이 되어야 한다.
외부에서 변경된다면, 캐쉬데이터와 저장소데이터간에 차이가 발생하게 된다.

실제로 Application Cache Object 를 아래와 같이 구현하였다.


 

    // Application 캐쉬 객체
    class AppCacheObject
    {
        private static HttpRuntime _httpRuntime;

        public static Cache Cache
        {
            get
            {
                EnsureHttpRuntime();
                return HttpRuntime.Cache;
            }
        }

        private static void EnsureHttpRuntime()
        {
            if (null == _httpRuntime)
            {
                try
                {
                    Monitor.Enter(typeof(AppCacheObject));
                    if (null == _httpRuntime)
                    {
                        _httpRuntime = new HttpRuntime();
                    }
                }
                finally
                {
                    Monitor.Exit(typeof(AppCacheObject));
                }
            }
        }

    } 

 

Cache 는 System.Web.Caching.Cache 의 객체이며, Get Property 로 사용한다.
System.Web 을 사용하기 때문에, System.Web.HttpRuntime 의 Cache 객체로 생성한다.

 

이는 Cache 객체에 대한 정의이며, 이를 이용하여,
캐쉬를 저장하는 SetCache
캐쉬를 조회하는 GetCache
캐쉬를 삭제하는 RemoveCache

 

여기서 한가지 중요한 것이, 캐쉬종류이다.
Absolute Expiration 과 Sliding Expiration 이라는것이 있다.

 

Absolute Expiration 은 지정된 만료시간이 지나면, 무조건 캐쉬가 삭제되는 것이고,
Sliding Expiration 은 동일하게 지정된 만료시간에 캐쉬가 삭제되지만, GetCache 를 할때마다 자동으로 만료시간이 연장이 된다.

 

일반적으로 로그인 처리를 하여, 로그인정보를 저장할때 Sliding Expiration Cache 를 사용하면,
지정된 시간이 지나면, 자동로그아웃이 되지만, 계속 작업을 한다면 (작업시마다 캐쉬를 사용하여 로그인정보를 확인) 로그인이 풀리지 않게 되는것이다.

 

실제로, AppCacheManager 라는 클래스로,
SetCache / GetCache / RemoveCache 와 슬라이딩 캐쉬를 구현한 것이다.
슬라딩캐쉬는 SetCache 에서만 지정해주면, GetCache / RemoveCache 는 동일하게 사용된다.

 

    // Copyright 헝그리개발자(https://bemeal2.tistory.com)
    // 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.

    // Application 캐쉬
    public class AppCacheManager
    {
        // 인증용 캐쉬 저장 : 슬라이딩 만료 캐쉬 - minute 후에 만료되지만, GetCache 요청이 있으면, 만료시간이 연장됨
        public static bool SetSlidingCache(string name, object value, int minute)
        {
            bool bvalue = true;

            try
            {
                AppCacheObject.Cache.Add(name, value, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(minute), CacheItemPriority.Normal, null);
            }
            catch
            {
                bvalue = false;
            }

            return bvalue;
        }

        // 캐쉬 저장 : 절대 시간 만료 캐쉬 - minute 후에 만료
        public static bool SetCache(string name, object value, int minute)
        {
            bool bvalue = true;

            try
            {
                AppCacheObject.Cache.Add(name, value, null, DateTime.Now.AddMinutes(minute), System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
            }
            catch
            {
                bvalue = false;
            }

            return bvalue;
        }

        // 캐쉬 get
        public static Object GetCache(string name)
        {
            Object ovalue = new Object();

            try
            {
                ovalue = AppCacheObject.Cache.Get(name);
            }
            catch { }

            return ovalue;
        }

        // 캐쉬 제거
        public static Object RemoveCache(string name)
        {
            Object ovalue = new Object();

            try
            {
                ovalue = AppCacheObject.Cache.Remove(name);
            }
            catch { }

            return ovalue;
        }

    } 

 

 

 

 

이를 이용하여, AppCacheExample 을 실행해보면,
로그인정보를 저장하는 Sliding Cache 와 일반 정보를 저장하는 Absolute Cache 를 사용하고 있다.
각각 캐쉬 만료시간을 1분으로 하여,
GetCache 를 했을때, 만료되는지와 자동연장되는지를 확인할 수 있다.

 

 

 

 

AppCacheExample.zip
다운로드

 

Posted by 헝개