회사에서 우리팀의 Windows Application 상용 프로그램을 만들어야 하는 이슈가 발생했다.

논의 끝에 VS 기반의 C#으로 기능을 구현하기로 하였고

Windows Application 개발 경험(개인 프로젝트인데.....;;;)이 있는 내가 설계부터 구현, 테스트까지 1인 프로젝트를 맡게 되었다.


나 역시도 C#이 주종목이 아니기에 무한한 지식의 조력자(Google-감사!)와 함께 진행하는 것으로 

내 프로그램의 모든 기능들은 C# 프로그래밍의 정석으로 만들어 지는 것이 아니다. 

그저 같은 기능을 구현함에 있어 수많은 구현 방식 중에 내 경험 및 지식에 빗대어 가장 유연하게 동작할 수 있는 방법을 채택하여 재조립 할 뿐.

 

때문에 구현하는 중간중간 다양한 버그 및 오류에 부딪친다.

 

이번 Exception Case는 기존 개인 프로젝트(L-ATS)를 진행하면서는 발생하지 않았던 문제였었는데 이번에 겪게되어 현재 내가 설계한 프로그램의 성격에 알맞는 방식으로 처리한 내용을 기록해 본다.

 

C#이나 WFP가 주종목이신 분들은...아주 사소한 문제일거 같다...


Ocurred Exception

System.ObjectDisposedException: '삭제된 개체에 액세스할 수 없습니다.'

Exception 발생 Case

  • Parent Form과 Child Form이 존재
  • Parent Form에서 Child Form.show()를 호출하여 Form을 띄우고 Child Form에 입력한 Data를 Parent Form에서 받아서 처리 (Delegate Event 사용)     

2020.06.20 - [C#] - [C#] delegate, 폼 간의 DATA 공유

 

[C#] delegate, 폼 간의 DATA 공유

Trading 시스템을 내가 아닌 제 3자의 사용자적 입장에서 사용하도록 시뮬레이션 해 보았을 때 자식 Form을 새로 띄워서 값을 입력 받거나 확인할 수 있게 하는 작업이 많이 필요하단 것을 알게 되

brandon-dev.tistory.com

  • Child Form에서 Data 입력 후 [ 확인 ] 시, Form Close() 후, 해당 Parent Form에서 Child Form을 다시 show() 할 때 발생

Form.show() 호출 시 ObjectDisposedException 발생

Child Form에서의 Close() 함수 호출에 의해 완전 삭제된 Child Form의 객체를 다시 show() 호출로 로딩하려고 하니 이미 삭제된 객체를 불러올 수 없다는 말이다.(인것 같다...)

Child Form 내부에서 self로 Close()를 하게되면 해당 객체를 생성한 Parent Form에서 객체 삭제를 하지 않아도 자동으로 삭제 된다는 것일까...?

 

사실 이 Disposed Exception은 비단 Form에 한정되어 나타나는 현상은 아니다. 

WinForm의 어떠한 객체라도 삭제된 후 접근하려 할 경우 같은 현상이 나타날테니 말이다. 

때문에 이 Exception을 해결하는 방법은 아주 다양하고 상황에 따라 합리적인 예시들이 많이 있다. 


내가 만들고 있는 프로그램의 Child Form의 역할은 

프로그램을 운용하기 위한 기본 설정 값들을 사용자에게 입력 받아 Parent Form으로 전달하는 역할을 한다.

쉽게 프로그램의 설정 POP-UP이라고 생각하면 된다. 

 

나는 Child Form의 [ FormClosing() ] 이벤트에 아래 코드를 추가하는 방식을 택했다.

private void micSetting_FormClosing(object sender, FormClosingEventArgs e)
{
    e.Cancel = true;
    this.Hide();
}

e.Cancel = true

FormClosing 작업을 취소하겠다는 것

this.Hide()

Form을 숨기겠다는 것

 

따라서 나는 분명 Child Form을 닫는 this.Close()를 호출하였지만 Form이 닫히는 과정(Form Closing)에서 Form이 닫히는 것을 취소하고 Form을 안보이도록 숨기겠다(Hide)는 뜻이다. 

 

여기서 중요한 점은.

Form을 사용자가 보지 못하게끔 숨기기만 하는 것으로 (사용자 입장에서는 Form이 꺼진것처럼 보이지만...)

이전에 Child Form에 입력한 값이 그대로 보인다는 것이다. Form을 숨기기만 하고 다시 보여주는 것이니 당연한 결과다.

 

프로그램의 설정값을 입력하는 목적의 Child Form을 만들 경우 사용자 입장에서는 이전에 입력한 값이 그대로 보여지니 이 방법이 내가 설계한 프로그램에는 가장 적합한 해결책인 것이다.

만약 Form을 삭제하고 다시 객체를 생성해서 Child Form을 띄울 경우 기존에 입력했던 값들을 다시 Parent Form에서 받아 오든, 아니면 특정 위치에 저장을 해서 불러오든 해야하기에 구현의 까다로움이 존재하므로 굳이 이 방법을 사용하지 않아도 좋은 프로그램을 만들 수 있다.

Posted by [ 브랜든 ]
,

학창 시절 C++ 시간에 배웠던 OOP, 그중에서도 Class의 강점에 대해 귀에 박히도록 들었던 말은...

'은닉성'

Encapsulation이라고도 하는 그놈이다. 

은닉성을 가장 쉽게 구현하는 방법은 class의 멤버는 외부에서 직접 접근 가능하지 못하도록 private 또는 protected로 선언하고 class에서 지정한 방법으로만 접근 가능하도록 public 함수를 두는 것이다. 

 

그러면 외부 사용자는 class의 멤버 변수들을 원하는 방식으로 조작할 수 없을 것이고, 그로 인한 장애나 버그도 발생할 일이 없을 것이다. 

 

class에 선언된 멤버 변수에 대해 값을 설정하고, 또 값을 얻어오는 setter와 getter에 대한 함수를 class 내부에 별도로 선언해 놓는 것이 일반적이다. 

class Test
{
	private int value;
    ...
    
    public void setValue(int value)
    {
    	if(value > 0)
            this.value = value;
    }
    
    public int getValue()
    {
    	return this.value;
    }
}

integer 멤버 변수 value에 대해 setter와 getter가 선언된 OOP적인(?) class의 모습이다. 

물론 C#에서도 위 형태의 class 사용이 가능하나...만약 class 내부에 변수가 30개면 어떻게 될까....?

setter와 getter가 각각 30개씩...엄청난 노가다와 가독성(readability)이 현저히 떨어질 것이다. 

 

그래서 C#에서는 속성(Property)라는 아주 좋은 기능을 제공한다.


C# Property

 

같은 기능을 하도록 Property를 사용해 보자. 아래처럼 변경하게 되면, 사용 방법이 바뀌는데 구조체 사용하듯이 사용하면 된다. 

class Test
{
    private int value;
    public int _value
    {
        get { return value; }
        set { this.value = value; }
    }
}

void main()
{
    Test test = new Test();
    
    test.value = 10;
    Console.WriteLine(test.value);
}

훨씬 간결해 지고 간단해졌다. 

 

C# 자동구현 Property

C#에서 제공하는 정말 간결하게 하는 방법이 있다. 

이 Property의 단점을 꼽자면...바로 '접근 제한자'가 public이어야 한다는 것 같다. 

class Test
{
    public int value { get; set; }
}

void main()
{
    Test test = new Test();
    
    test.value = 10;
    Console.WriteLine(test.value);
}

 

MS Visual Studio에서는 Property 작성을 쉽게 할 수 있는 방법을 제공한다.

 

[prop]을 입력하고 [Tab] 키를 두 번 누르면 자동구현 Property 코드가 입력된다.

[propfull]을 입력하고 [Tab] 키를 두 번 누르면 C# Property 코드가 입력된다.

namespace WinFormTest
{
    public partial class Form1 : Form
    {
        // prop + Tab 두번
        public int MyProperty { get; set; }

        // propfull + Tab 두번
        private int myVar;

        public int MyProperty
        {
            get { return myVar; }
            set { myVar = value; }
        }
    }
}

 

입력된 코드의 class 명, 변수명만 원하는 값으로 변경해서 사용하면 끝.

 

Posted by [ 브랜든 ]
,

C#에 대한 공부를 하면서 L-ATS v2.0 개발을 진행하다보니 생각했던대로 되지 않거나 내 설계 방향으로는 구현이 불가능한 부분들이 종종 발생한다. 

 

앞서 포스팅한 '주식종목'에 대한 클래스(객체) 사용을 객체 배열(Array)을 사용하였는데...(단순히 C/C++ 개발자로써 구조체 배열을 생각했기에...) 사실, 데이터의 빠른 접근 때문에 배열(Array)자체를 매우 선호하는 편이다. 

객체 배열을 DatagridView에 Binding하는 과정에서 C#에서는 Collection(콜렉션)이란 강력한 도구를 제공한다는 것을 알았다. C# 기본서와 구글링을 통해 알게 된것은 C언어에서처럼 구조체 배열과 같은 형태의 데이터 구조는 DataGridView에 Binding하기에 적합하지 않은것 같다.

 

C#에서 제공하는 ArrayList와 Collection, 그리고 Generic Collection에 대해 짚고 넘어가야겠다. 

이미 '주식종목' 부분의 설계와 개발이 거의 끝나가고 있는데...뒤집어 엎어야 할거 같다....


컬렉션(Collection)

   - 같은 Type의 데이터를 모아서 처리하는 데이터 구조.

   - 배열(Array)은 컬렉션 중의 하나라고 할 수 있음.

   - 컬렉션은 Generic Collection 과 Non-Generic Collection이 있음.

 

ArrayList

   - 배열과 유사한 자료구조.

   - 배열과 달리 그 크기를 미리 지정하지 않고 동적으로 할당 가능.

   - 모든 타입의 데이터가 저장될 수 있음. (Parameter의 Type이 Object임)

   - 관리하는 데이터가 많아질 수록 속도가 느려짐.

 

수행할 작업 Generic Collection Non-Generic Collection
키별로 빠르게 접근할 수 있도록 키/값의 쌍으로 저장 Dictionary(Tkey, TValue) Hash Table
인덱스 별로 항목 액세스 List<T> Array
ArrayList
FIFO 방식으로 항목 사용 Queue<T> Queue
LIFO 방식으로 항목 사용 Stack<T> Stack
순서대로 항목 액세스 LinkedList<T> -
컬렉션에 항목을 추가하거나 삭제할 떄 알림 표시 ObserverbleCollection<T> -
정렬된 컬렉션 SortedList<t> SortedList
수학 함수용 집합 HashSet<T>
SortedSet<T>
-

위의 내용을 보면...Generic Collection의 경우 C#에서만 제공하는 Optimized 도구들인것 같은 느낌이다. 

Non-Generic Collection의 경우는 구식 도구들인것 같은 느낌...C#에서는 두 가지 형태 모두 제공한다는 것이고.

사실 Generic Collection은 컴파일 타입에 Type-Safe(형식이 안전한)하기에 Non-Generic Collection보다 성능이 좋다고 한다. 

 

Dictionary는 Python에서도 자주 사용하는 Data 구조로 그 활용성이 매우 좋음을 알고 있기에, 내가 만들려는 '주식종목' 목록에 대해 Dictionary의 TValue가 Object 형식으로 저장이 가능하다면 Dictionary를 사용해야겠다.

 


Dictionary(딕셔너리)

'사전'. 사전을 펼쳐보면 가장 마지막 부분에 Index가 있다. 몇몇의 [키워드]들이 나열되어 있고, 그 키워드에 대한 [내용]이 몇 페이지에 존재하는지 표시되어 있는...

때문에 찾기 원하는 내용의 [키워드]를 알고 있으면 빠르게 [내용]을 찾을 수 있다. 

Dictionary의 원리는 이것과 같다. 

Array(배열)도 마찬가지다. 원하는 데이터가 저장되어 있는 Index(배열의 Index)를 알면 바로 데이터를 가져올 수 있다. 

Dictionary는 Index 대신, 특정 Key 값(사용자가 원하는)을 사용할 수 있기에 보다 인지하기 쉽고 가독성(Readability)이 좋다.

< 주의 사항 >
1. 중복된 키(Tkey)를 사용할 수 없음. 'ArgumentException' 발생.
2. 존재하지 않는 키(Tkey)로 검색 시 'KeyNotFoundException' 발생.
3. Array와 같이 순차적인 숫자 Index 사용 불가.

 

다음은 C# 기본서에 있는 Dictionary 예제이다. 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Dictionary
{
    class Program
    {
        static void Main(string[] args)
        {
            Dictionary<string, string> colorTable = new Dictionary<string, string>();

            colorTable.Add("Red", "빨간색");
            colorTable.Add("Green", "초록색");
            colorTable.Add("Blue", "파란색)");

            foreach (var v in colorTable)
                Console.WriteLine("colorTable[{0}] = {1}", v.Key, v.Value);

            // 동일 Key 값을 추가하는 경우
            try
            {
                colorTable.Add("Red", "빨강");
            }
            catch(ArgumentException e)
            {
                Console.WriteLine(e.Message);
            }

            // 없는 Key 값을 찾는 경우
            try
            {
                Console.WriteLine("Yellow => {0}", colorTable["Yellow"]);
            }
            catch(KeyNotFoundException e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}

Add() 함수가 쉽게 Dictionary Item을 추가할 수 있도록 해주며, Tkey의 위치의 영문값이 Key가 되어 빠르게 TValue에 해당하는 값을 읽어올 수 있는 구조이다. 

 

Dictionary<string, string>은 TKey와 TValue를 모두 'string' Type으로 지정하여 Dictionary를 생성하였으므로, 객체를 사용할 결루 Dictionary<string, object명>을 사용하면 될 것 같다. 

그럼...TValue 쪽에 해당하는 객체(object)는...DataGridView에 어떻게 또 Binding을 시킬 수 있을지...걱정이 앞서긴 하지만...다음 C# 포스팅은 Dictionary를 DataGridView에 Binding하는 걸 해야겠다. 

 

 

 

 

Posted by [ 브랜든 ]
,

C언어를 주로 사용하더 나에게는 이 Class 라는 것이 이해하기 쉬우면서도 이해하기 어려운 부분이었다.

흔히 Class와 Sturct(구조체)를 비교하는데 Struct는 평소에 아주 많이 사용하는 놈이기 때문이었다.

 

C에서의 구조체의 정의는 너무도 쉽다. 

함께 쓰고 싶은 다양한 데이터 Type 변수들의 집합. 모음. 모둠..? 

그리고 OOP를 흉내낼 수 있도록 해준 '함수 포인터'도 멤버로 가질 수 있다. (C#에서는 함수를 가질수 있다는 점이 가장 큰 차이점일까.)

 

구조체와 클래스의 차이점에 대해 정리되어 있는 것은 이보다 더 많은 자료들을 찾아볼 수 있다.

구분 구조체 (STRUCT) 객체 (CLASS)
형식 Value (값 형식) Reference (참조 형식)
생성영역 Stack Heap
인스턴스 new () 선언
상속 X O
생성자 X O
인터페이스 O O
STATIC X O

 


 

구조체의 경우 구조체 배열을 사용하여 동일한 형태의 여러 데이터들의 집합을 관리할 수 있다. 

이 기능은 프로그램 내의 데이터 관리 측면에 있어서 엄청난 효율성을 가져다 준다. 

 

게다가 배열이다. Index를 사용해서 가장 빠른 속도로 원하는 데이터에 접근할 수 있으니, 개발자의 입장에서는 최고의 조합이다. C#의 클래스도 같은 구조의 기능을 지원할까.

 

 

나는 아래와 같이 객체 배열을 만들어서 사용하고 싶었다. 

 

 

제작중인 자동매매 프로그램의 '종목'관련 Class 상속 관계

 

 

부모 클래스의 변수 및 함수를 상속받은 자식이 사용하도록, 그것도 외부 클래스에서 사용하도록 하기 위해 부모클래스의 변수에 대해 '접근 제한자'를 사용하도록 하는 데에도 몇번의 시행착오를 겪었다. 

 

부모 클래스의 변수를 상속받은 자식 클래스에서만 접근이 가능하도록 'protected'로 선언하였는데 아래 표의 설명이 많은 도움이 되었다.

 

 C# 접근 제한자
public
클래스의 내부와 외부 모든 곳에서 접근이 가능하다.
protected
클래스의 내부와 해당 클래스를 상속 받은 자식 클래스에서만 접근이 가능하다.
private
해당 클래스 내부에서만 접근이 가능하다.

 

 

그리고 외부 클래스에서 자식 클래스인 Stock_RTB를 객체 배열로 선언하여 사용하기 위해 C# 기초 책과 많은 포스팅에서 아래와 같이 간단하게 적용했는데....

이상하게 계속 NullReference Exception이 발생하는 것이다. 

using System;

namespace Program
{
	public partial class MainClass : Form
    {
    	... 생략 ...
        
        // RTB 클래스 배열 선언
        Stock_Realtime_Balance[] gArrStockRTB;
        
        
        ... 생략 ...
        
        string hname = "이베스트증권";
        
        gArrStockRTB = new Stock_Realtime_Balance[3];
        
        for (int index = 0; index < 3; i++)
        {
            try
            {
                gArrStockRTB[index].setCodeName(hname);
                ... 생략 ...
                
                string line = String.Format("{0}", gArrStockRTB[index].getCodeName());
                ... 생략 ...
            }
            catch (NullReferenceException e)
            {
                Console.WriteLine(e);
            }
        }
         ... 생략 ...
    }
}               
                

와...정말 온갖 부분을 다 예외 처리하고 로그를 찍어서 확인해 봤지만 도저히 찾지를 못했는데, 2일만에 드디어 문제가 됐던 부분을 찾았다. (2일이라면 오래 걸린것 같지만...이 프로그램을 만드는데에 하루에 1.5시간 정도만 투자하고 있으므로 생각보다 빨리 찾은 셈이다.)

using System;

namespace Program
{
	public partial class MainClass : Form
    {
    	... 생략 ...
        
        // RTB 클래스 배열 선언
        Stock_Realtime_Balance[] gArrStockRTB;
        
        
        ... 생략 ...
        
        string hname = "이베스트증권";
        
        gArrStockRTB = new Stock_Realtime_Balance[3];
        for (int i = 0; i < gArrStockRTB.Length; i++)
            gArrStockRTB[i] = new Stock_Realtime_Balance();
        
        for (int index = 0; index < 3; i++)
        {
        	try
        	{
        		gArrStockRTB[index].setCodeName(hname);
                ... 생략 ...
                
                string line = String.Format("{0}", gArrStockRTB[index].getCodeName());
                ... 생략 ...
            }
            catch (NullReferenceException e)
            {
                Console.WriteLine(e);
            }
        }
         ... 생략 ...
    }
}

다시 각 객체 배열의 위치마다 new로 잡아줘야 한다니...객체(Class)의 특성상 당연한 작업인데...

좀 더 세련되게 'Extension Method'를 사용해서 심플하게 처리하는 방법이 있다는데...C# 초보자인 나는 일단 for문을 사용하기로 했다....

 

역시 정확하게 다 이해하지 못한 상황에서 개발부터 진행하면 이렇게 간단한 실수를 하게된다.

프로그래밍은 재밌다.

 

 

Posted by [ 브랜든 ]
,

C언어만 사용하던 나는 솔직히 OOP 개념이 익숙하거나 크게 와 닿지 않는다.

 

중복된 기능의 함수나 DB Handler, Logger 등의 기능 단위를 구분할 수 있는 것들을 모듈화하여

Library 형태로 만들어 컴파일 할 때 추가하여 사용하곤 했다. 

 

C#에서는 하나의 프로젝트 안에 여러 클래스를 생성하여 사용하는 것 같으니 방법을 정리해 보자.

 

delegate 테스트했던 프로젝트에 추가해서 진행하면 좋을것 같다.

2020/06/20 - [C#] - [C#] delegate, 폼 간의 DATA 공유

[C#] delegate, 폼 간의 DATA 공유

Trading 시스템을 내가 아닌 제 3자의 사용자적 입장에서 사용하도록 시뮬레이션 해 보았을 때 자식 Form을 새로 띄워서 값을 입력 받거나 확인할 수 있게 하는 작업이 많이 필요하단 것을 알게 되��

brandon-dev.tistory.com

Form2에서 입력했던 '아이디'와 '비밀번호'를 Form1에서 바로 출력하지 않고

새로운 클래스 파일(.cs)의 객체에 저장한 후에 그 값을 읽어와서 출력하는 방법으로 진행해 보자.

 

오른쪽 솔루션 탐색기에서 프로젝트 명의 오른쪽 클릭 후 [ 추가(D) ] - [ 새 항목(W) ]을 선택한다.

 

 

프로젝트 오른쪽 클릭 - [ 추가(D) ] - [ 새 항목(W) ]

 

 

클래스를 선택 후에 입력할 사용자 정보를 관리할 'User_Info.cs' 파일을 추가한다.

 

 

User_Info.cs 파일 추가

 

User_Info Class는 ID와 Password, 두 개의 변수를 갖고, 각각의 값을 Set/Get하는 함수를 갖게 될 것이다.

 

ID와 비밀번호는 사용하는 Class에서 직접 접근할 수 없도록 'private' 접근 제한자를 사용하고, 각각의 값을 Handling 할 수 있는 'public' 함수를 만들어 사용할 수 있도록 한다. (이게 객체지향 방법이라는데...나는 익숙치 않아서....Anyway)

 

[ User_Info.cs ]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace delegate_test
{
    class User_Info
    {
        private string Id;             // 사용자 ID
        private string Password;       // 사용자 비밀번호

        // 생성자
        public User_Info()
        {
            Id = string.Empty;
            Password = string.Empty;
        }

        public void SetId(string Id)
        {
            this.Id = Id;
        }

        public string GetId()
        {
            return this.Id;
        }

        public void SetPassword(string Password)
        {
            this.Password = Password;
        }

        public string GetPassword()
        {
            return this.Password;
        }
    }
}

참고로 나는 변수의 초기화를 굉~~장히 중요하게 생각하는 개발자다. (생성자에서 변수 초기화는 무조건 필수!!)

 

이제 User_Info Class를 어떻게 사용하는가...

두 가지의 방법이 있는데, 하나는 User_Info Class 내에 static 객체를 하나 생성해두고 다른 곳에서 호출해서 쓰는 방식이고 다른 하나는 User_Info Class를 사용할 곳에서 객체를 직접 생성해서 사용하는 방식이 있다. 

 

< 방법 1> - Static 객체

[User_Info.cs]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace delegate_test
{
    class User_Info
    {
        // static 객체
        public static User_Info gUser_Info = new User_Info();

        private string Id;             // 사용자 ID
        private string Password;       // 사용자 비밀번호

        // 생성자
        public User_Info()
        {
            Id = string.Empty;
            Password = string.Empty;
        }

        public void SetId(string Id)
        {
            this.Id = Id;
        }

        public string GetId()
        {
            return this.Id;
        }

        public void SetPassword(string Password)
        {
            this.Password = Password;
        }

        public string GetPassword()
        {
            return this.Password;
        }
    }
}

어디서든 접근 가능하도록 public으로 만든 static 객체를 만들어 둔다.

 

사용하는 방법은 간단하다.

User_Info 객체를 사용하고 싶은 곳에서 [클래스명].[객체명].[함수/변수]와 같은 형태로 사용하면 된다.

[Form1.cs]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace delegate_test
{
    public delegate void LoginGetEventHandler(string id, string password); // 자식 Form EventHandler

    public partial class Form1 : Form
    {
        static string static_id = "brandon";
        Form2 Child = new Form2(static_id);

        public Form1()
        {
            InitializeComponent();
            Form2.LoginGetEvent += new LoginGetEventHandler(this.Login_Process); // EventHandler 등록
        }

        private void 로그인ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Child.Owner = this;
            Child.ShowDialog();                     
        }

        private void Login_Process(string id, string password) // Event 발생 시, 처리할 함수 선언
        {
            // User_Info 객체에 입력 받은 정보 저장
            User_Info.gUser_Info.SetId(id);
            User_Info.gUser_Info.SetPassword(password);


            // User_Info 객체에서 값 가져오기
            string test_result = string.Empty;
            //test_result = String.Format("ID : {0}, PW : {1}", id, password);
            test_result = String.Format("ID : {0}, PW : {1}", User_Info.gUser_Info.GetId(), User_Info.gUser_Info.GetPassword())
            textBox1.Text = test_result;  // 자식 Form에서 수신한 값 표시
        }
    }
}

 

이전과 동일하게 비밀번호를 'asdf1234!@#$'로 입력 후 로그인 버튼 클릭

 

전과 동일하게 아이디랑 비밀번호가 출력되면 정상적으로 User_Info Class를 사용하는 셈.

 

 

자식 Form에서 입력한 값을 부모 Form에서 받아 User_Info 객체에 Set/Get 후 출력 

 

 

사용자 정보의 경우, 프로그램내에서 단 하나만(!) 존재하는 것이기에 위의 예제처럼 static 객체로 선언해서 쓰는것이 좋을 것 같다. 상황에 따라 여러개의 객체가 필요한 경우는 아래처럼 사용하면 된다.

 

[Form1.cs]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace delegate_test
{
    public delegate void LoginGetEventHandler(string id, string password); // 자식 Form EventHandler

    public partial class Form1 : Form
    {
        // User_Info 객체 생성
        User_Info gUser_Info = new User_Info();

        static string static_id = "brandon";
        Form2 Child = new Form2(static_id);

        public Form1()
        {
            InitializeComponent();
            Form2.LoginGetEvent += new LoginGetEventHandler(this.Login_Process); // EventHandler 등록
        }

        private void 로그인ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Child.Owner = this;
            Child.ShowDialog();                     
        }

        private void Login_Process(string id, string password) // Event 발생 시, 처리할 함수 선언
        {
            // User_Info 객체에 입력 받은 정보 저장
            gUser_Info.SetId(id);
            gUser_Info.SetPassword(password);


            // User_Info 객체에서 값 가져오기
            string test_result = string.Empty;
            //test_result = String.Format("ID : {0}, PW : {1}", id, password);
            test_result = String.Format("ID : {0}, PW : {1}", gUser_Info.GetId(), gUser_Info.GetPassword());
            textBox1.Text = test_result;  // 자식 Form에서 수신한 값 표시
        }
    }
}

 

Posted by [ 브랜든 ]
,

Trading 시스템을 내가 아닌 제 3자의 사용자적 입장에서 사용하도록 시뮬레이션 해 보았을 때

자식 Form을 새로 띄워서 값을 입력 받거나 확인할 수 있게 하는 작업이 많이 필요하단 것을 알게 되었다. 

(기능적으로 그렇지만 보기에, 사용하기에 신규Form을 보여주는 것이 더 깔끔한...)

 

  • 부모 Form : Form1 (Delegate Test) 

'로그인' 버튼과 결과를 출력해줄 TextBox를 갖는 부모 Form

  • 자식 Form : Form2 (Login)

'아이디'와 '비밀번호'를 입력할 수 있는 TextBox와 로그인 버튼을 갖는 자식 Form

 

테스트 구현 방식은

부모 Form(Form1)에서 아이디 값을 넘겨주어 자식 Form(Form2)에서는 아이디가 자동으로 입력되고

자식 Form에서 비밀번호 입력 후 '로그인' 버튼을 누르면 

자식 Form이 사라짐과 동시에 부모 Form의 TextBox에 입력받은 비밀번호를 출력하도록 한다. 

 

그러면 두 Form 간에 데이터를 주고 받는 테스트가 완료!

 

1. 부모 Form -> 자식 Form

 

여러 다른 방법이 있는지는 모르겠지만, 내가 구현한 방법은 생각보다 간단하다.

 

[ 부모 Form ]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace delegate_test
{
    public partial class Form1 : Form
    {
        static string static_id = "brandon";          // 자식 Form에 넘겨줄 ID 값
        Form2 Child = new Form2(static_id);           // 자식 Form 생성자

        public Form1()
        {
            InitializeComponent();

        }

        private void 로그인ToolStripMenuItem_Click(object sender, EventArgs e)  // 로그인 버튼을 눌렀을 때
        {
            Child.Owner = this;
            Child.ShowDialog();                     
        }
    }
}

[ 자식 Form ]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace delegate_test
{
    public partial class Form2 : Form
    {
        string global_id = string.Empty;

        public Form2(string id)           // 자식 Form의 생성자 - 부모 Form으로부터 값을 전달 받음
        {
            InitializeComponent();
            global_id = id;
        }

        private void Form2_Load(object sender, EventArgs e) // Form2가 보여질때 설정
        {
            textBox1.Text = global_id;    // Form2에 부모 Form으로부터 받은 id 값을 미리 설정
        }

        private void button1_Click(object sender, EventArgs e)
        {

        }
    }
}

[ 결과 ]

자식 Form(Login)이 표시됨과 동시에 부모 Form에서 받은 'brandon'이란 ID 값이 자동으로 입력됨.

 

2. 자식 Form -> 부모 Form

 

이 경우에는 delegate라는것을 사용하는데, C++에서의 함수포인터와 비슷한 개념이다.

'대리자'라는 뜻의 이 delegate는 메소드를 대신해서 호출해 주는 역할을 한다고 생각하면 된다. 

 

[ 부모 Form ]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace delegate_test
{
    public delegate void LoginGetEventHandler(string id, string password); // 자식 Form EventHandler

    public partial class Form1 : Form
    {
        static string static_id = "brandon";
        Form2 Child = new Form2(static_id);

        public Form1()
        {
            InitializeComponent();
            Form2.LoginGetEvent += new LoginGetEventHandler(this.Login_Process); // EventHandler 등록
        }

        private void 로그인ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Child.Owner = this;
            Child.ShowDialog();                     
        }

        private void Login_Process(string id, string password) // Event 발생 시, 처리할 함수 선언
        {
            string test_result = string.Empty;
            test_result = String.Format("ID : {0}, PW : {1}", id, password);
            textBox1.Text = test_result;  // 자식 Form에서 수신한 값 표시
        }
    }
}

[ 자식 Form ]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace delegate_test
{
    public partial class Form2 : Form
    {
        public static LoginGetEventHandler LoginGetEvent;  // 부모 Form에서 등록한 EventHandler 선언

        string global_id = string.Empty;

        public Form2(string id) 
        {
            InitializeComponent();
            global_id = id;
        }

        private void Form2_Load(object sender, EventArgs e) 
        {
            textBox1.Text = global_id;
        }

        private void button1_Click(object sender, EventArgs e) // 로그인 버튼을 눌렀을 때
        {
            this.Close();
            LoginGetEvent(textBox1.Text, textBox2.Text);  // 입력된 ID와 PASSWORD 값을 EventHandler에 전달
        }
    }
}

[ 결과 ]

비밀번호를 'asdf1234!@#$'로 입력 후 로그인 버튼 클릭
자식 Form에서 입력한 값을 부모 Form에서 받아서 출력

 

나도 C#을 처음 해보는 것이기에 더 좋고, 간단하며, 오류 없는 방법이 있을지는 모르겠다. 

Trading 시스템의 '사용자 로그인' 부분을 구현하면서 필요한 기능이었기에 어떻게 활용하였는지는

아래 링크에서 확인해 보자. 

Posted by [ 브랜든 ]
,

[C#] MAC Address 가져오기

C# 2020. 6. 15. 23:51

Trading 시스템을 만들면서 사용자 인증방식을 지정된 단말로만 가능하도록

'MAC 인증' 방식을 택했다.

 

물론 내가 C#을 처음써서인 것도 있겠지만, MAC 주소를 읽어올때 마다 값이 다른 것을 확인했다.

이유인 즉, PC에 설치된 VPN...가상 환경 때문이었다.

 

구글링 결과 C#에서 기본적인 단말 MAC 주소를 가져오는 로직은 NetworkInterface들중에 0번째 것을 가져오도록 하는 소스가 대부분이었다. 

 

[ 방법 1 ]

string mac = string.Empty;
mac = NetworkInterface.GetAllNetworkInterfaces()[0].GetPhysicalAddress().ToString();

가상 환경이 설치되어 있는 PC의 경우, 해당 가상환경 사용중인 여부에 따라 값이 다르게 읽어지는 것을 확인했다.

 

그래서 찾은 방법은 현재 상태가 사용중인 네트워크 어댑터의 MAC 주소를 가져오는 것.

 

[ 방법 2 ]

var macAddr =
    (
        from nic in NetworkInterface.GetAllNetworkInterfaces()
        where nic.OperationalStatus == OperationalStatus.Up
        select nic.GetPhysicalAddress().ToString()
    ).FirstOrDefault();

OperationalStatus가 "Up"으로 설정되어 있는 놈을 가져오는 것이다. 

위의 경우에도 현재 시스템의 상태에 따라 정확하게 MAC 주소를 가져오는 것을 확인했다.

실제 물리적 네트워크 어댑터만 사용 중

[ 방법 1 ]을 사용했을 때는 '이더넷 3'인 가상 네트워크 어댑터의 MAC주소를 가져왔지만, [ 방법 2 ]를 사용했을 경우에는 현재 사용중인 '이더넷 2' 네트워크 어댑터의 MAC주소를 가져왔다.

 

하지만, 가상환경도 함께 연결되어 있는 경우, 보다 정확하게 하기 위해 현재 데이터 송/수신량까지 확인하여 가장 많이 사용되고 있는 네트워크 어댑터의 MAC 주소를 가져오는 방식을 사용하는 방법도 있다. 

가상환경 네트워크 어댑터 동시 사용 중

[ 방법 3 ]

Dictionary<string, long> macAddresses = new Dictionary<string, long>();
foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
{
    if (nic.OperationalStatus == OperationalStatus.Up)
        macAddresses[nic.GetPhysicalAddress().ToString()] = nic.GetIPStatistics().BytesSent + nic.GetIPStatistics().BytesReceived;
}

long maxValue = 0;
string mac = "";
foreach(KeyValuePair<string, long> pair in macAddresses)
{
   if(pair.Value > maxValue)
    {
        mac = pair.Key;
        maxValue = pair.Value;
    }
}

먼저 딕셔너리에 네트워크 어댑터들의 MAC 주소와 데이터 송/수신량을 쌍(Pair)로 저장 후,

비교하여 송/수신량이 가장 많은 MAC 주소를 가져오는 로직이다. 

 

아래 웹 페이지에 MAC 주소를 가져오는 다양한 방법이 기술되어 있으니, 본인의 개발 방향과 맞는 방법을 사용하면 좋을 것 같다.

 

[ 출처 ]

https://www.it-swarm.dev/ko/c%23/c-%EC%97%90%EC%84%9C-%EC%BB%B4%ED%93%A8%ED%84%B0%EC%9D%98-mac-%EC%A3%BC%EC%86%8C%EB%A5%BC-%EC%96%BB%EB%8A%94-%EC%95%88%EC%A0%95%EC%A0%81%EC%9D%B8-%EB%B0%A9%EB%B2%95/957550098/

Posted by [ 브랜든 ]
,

Class...객체지향에서 빼놓을 수 없는.

Class에는 항상 상속, 오버로딩, 오버라이딩 그리고 캡슐화가 항상 붙어 다닌다.

 

절차적 언어인 C언어를 주 언어로 사용하던 나는

솔직히 객체지향 방식이 쉽게 와닿지 않았다. (사실은 지금도 그렇다....)

그래서 Python을 써도 C언어를 주로 쓰던 시절처럼 Library 함수 형태로 만드는 것이 익숙하고

Class 형식의 Library를 만드는 것이 덜 익숙하다. 

 

그래도 Class에 대해 정리를 해두면 필요할 경우 곧잘 사용할 수 있겠지. 

 

C언어를 사용하던 나에게는 구조체(Struct)라는 개념은 아주 많이 익숙하다. 

때때로 C# 책을 만든 저자들은 클래스와 구조체를 크게 구분하지 않으려 하지만 나와 같은 개발자에게는

분명하게 개념상의 차이점이 있다. 

 

  • 클래스 및 구조체에는 데이터와 동작을 나타내는 멤버가 있다. (사실 8년여간 C 프로그래밍을 해오면서 단 한번도 구조체 않에 멤버 함수를 선언해서 사용해 본 적이 없다!!)
  • 클래스가 상속 받았다면, 클래스에서 선언된 모든 멤버와 함께 상속받은 클래스에서 선언된 모든 멤버들(생성자 및 소멸자 제외)도 클래스의 멤버에 포함된다.
멤버 설명
필드 필드는 클래스 또는 구조체에서 직접 선언되는 모든 형식의 변수이다. 필드는 기본 제공 형식 또는 다른 클래스의 인스턴스일 수 있다.
상수 상수는 값이 컴파일 시간에 설정되며, 변경할 수 없는 필드나 속성이다.
속성 해당 클래스의 필드처럼 액세스되는 클래스의 메소드로 객체 모르게 필드가 변경되지 않도록 할 수 있다.
메소드 클레스가 수행하는 작업을 정의한다. 입력으로 매개변수를 사용할 수 있고, 매개변수를 통해 출력 데이터를 반환할 수 있다. 매개변수를 통하지 않고 직접 값을 반환할 수도 있다.
이벤트 이벤트는 버튼 클릭, 성공적인 메소드 완료 등의 사건이 발생했을 때 알림을 다른 객체에 제공한다.
이벤트는 대리자(delegate)를 사용하여 정의 및 트리거 된다.
연산자 오버로드 된 연산자는 클래스 멤버로 간주되며, 클래스에서 'public static' 메소드로 정의한다.
인덱서 인덱서를 사용하면 배열과 유사한 방식으로 객체를 인덱싱할 수 있다. 
생성자 생성자는 객체를 처음 만들 때 호출되는 메소드이다. 보통 객체의 데이터를 초기화하는데 사용한다.
소멸자 C#에서 드물에 사용되며, 메모리에서 객체를 제고할 때 런타임 실행 엔진이 호출하는 메소드이다.

 

C# 공부를 위해 구매한 책에서 발췌한 내용이다. 

다른 것들은 한번쯤은 들어보거나 사용해봤던 것들인데, '이벤트'와 '인덱서'는 조금 새롭다. 

 

C 언어로 프로그래밍 할 때 '구조체 배열'을 자주 사용했었는데, '인덱서'를 사용하면 비슷한 기능을 구현할 수 있을 것 같다. 

'이벤트' 요소는 .NET 계열에서 특화적으로 사용되는 것으로 생각되는데(그냥 내 생각....), 내가 구현하고자 하는 Trading System에서 아주 많이 사용될 기능으로 보인다. 

 

  1. 필드 (Field)
  • 인스턴스(객체) 필드 : 객체 이름과 함께 사용. public string name과 같이 [접근제한자][자료형][필드명]으로 생성하고 [인스턴스].[필드명]으로 사용.
  • 정적(클래스) 필드 : 클래스 이름과 함께 사용. static 키워드 필수. [클래스이름].[필드명]
  1. 상수
  • 컴파일 시간에 알려진 변경할 수 없는 값. 프로그램 수행되는 공안 갑승ㄹ 변경하지 않는 경우 사용.
  • const 한정자 사용.
  • bool, byte, char, int double, string만 const 사용 가능.
  • Access : [클래스이름].[상수명]

 

 

Posted by [ 브랜든 ]
,

WinApp이나 Explorer에서 항상 해보고 싶던 팝업 띄우기.

 

제공하는 라이브러리가 있을테니 별것 아닐거라 생각은 했지만 실제로 해보게 되니

그 또한 즐겁구나.

 

1. 팝업을 띄울 버튼 생성

 

모달, 모달리스 'Button' 생성

 

2. 팝업으로 보여줄 새로운 Form 추가

 

Project > Add Windows Form

새로운 Form2를 생성

 

3. 팝업창 만들기

 

 

4. 팝업을 띄우는 방법

모달 모달리스
1. showDialog()
2. 새로운 Form이 프로그램의 제어권을 독점.
3. 다른 작업 불가능.
4. 중요한 메시지를 표시할 때 사용.
1. show()
2. 새로운 Form이 프로그램의 제어권을 독점하지 않음.
3. 다른 작업 가능.
4. 알림 또는 정보를 표시하는데 사용.
using System.Windows.Forms;

namespace WinFormTest
{
	public partial class Form1 : Form
    {
    	public Form1()
    	{
            InitializeComponent();
        }
        
        // 모달
        private void button1_Click(object sender, EventArgs e) 
        {
            Form2 modal = new Form2();
            modal.showDialog()
        }
        
        //모달리스
        private void button2_Click(object sender, EventArgs e)
        {
            Form2 modalless = new Form2();
            modalless.show();
        }
    }
}

 

5. 창 닫기

 

팝업 화면에 닫기 버튼이 있다면...

using System.Windows.Forms;

namespace WinFormTest
{
    public partial class Form2: Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    }
}

 

  • 그럼...팝업의 닫기 버튼으로 Form1까지 종료시킬 수 있을까..?

     - 이벤트를 추가하면 된다는데...?

 

6. 원하는 위치에 보이고 싶을때

 

사용자 입장에서 볼 때, 상위(부모) Form의 가운데에 팝업을 띄우는 것이 가장 보기 좋을 것이다.

방법...?

C#에서 제공해 주는 라이브러리가 없다면....

상위(부모) Form의 크기와 팝업 Form의 크기를 구해서 위치를 변경해주는 수밖에...

이것 저것 해보다 보니 C#에서 제공되는 쉬운 방법이 있었다.

private void button1_Click(object sender, EventArgs e) 
{
    Form2 modal = new Form2();
    modal.StartPosition = FormStartPosition.CenterParent; // 부모 Form의 가운데
    // modal.StartPosition = FormStartPosition.CenterScreen; // 모니터의 가운데
    modal.showDialog()
}

와우...정말 손쉽게 이런 기능을...

C언어 쓰던 자괴감이 든다...

'C#' 카테고리의 다른 글

[C#] delegate, 폼 간의 DATA 공유  (2) 2020.06.20
[C#] MAC Address 가져오기  (0) 2020.06.15
[C#] Class, 클래스, 구조체  (0) 2020.06.08
[C#] 값을 표현하기 위한 방법  (0) 2020.05.28
[C#] 눈에 보이는 재미  (0) 2020.05.24
Posted by [ 브랜든 ]
,

GUI로 표현되는 개발 언어들은 다양하고 편리한 기능들을 제공해준다.

 

예를 들면, 값을 표시해주는 방법 같은 것.

 

1. 그룹 표시자

 

   숫자의 정수부를 표시할 때, 특히 금액을 표시할 때 세자리마다 콤마(,)를 넣는 것이 인식하기 편리하다.

   C#에서 제공하는 표준 형식지정자 중에 N이 그룹 분리자를 표시해준다.

  • 표준 형식 지정자 'N'은 Default로 소수점 아래 두자리까지 표시해 준다. 
double value = 1234.5678;

string.Format("{0:N}", value);      // 출력 : 1,234.57  (Default 소수점 두자리)
string.Format("{0:N0}", value);     // 출력 : 1,235     (소수점 삭제)
string.Format("{0:N3}", value);     // 출력 : 1,234.568 (소수점 세자리)

 

2. String.Format()

 

   String.Format() 메소드는 개발자가 지정한 형식에 따라 객체, 변수, 수식의 값을 문자열로 변환하여 문자열 변수에

   저장해 준다.

  •    통화 (Currency)는 'C'
  •    날짜 (Date)는 'd'
  •    시간 (Time)은 't'
  •    % (Percent)는 'P'
String str;
Decimal ExchangeRate = 1240.00m;
double percent = 1.25d;

str = String.Format("원달러 환율 : {0:C}", ExchageRate);    // W1,240
str = String.Format("원달러 상승률 : {0:P}", percent);      // 1.25%
str = String.Format("날짜 : {0:d}", DateTime.Now);          // 2020-05-28
str = String.Format("시간 : {0:t}", DateTime.Now);          // 오후 8시 12분

 

사용자 지정 형식 문자열은 종류도 많을 것이고 어떻게 활용하냐에 따라 나열하기에는 끝도 없을 것이다.

우선 필요한것 만 정리할 뿐...

'C#' 카테고리의 다른 글

[C#] delegate, 폼 간의 DATA 공유  (2) 2020.06.20
[C#] MAC Address 가져오기  (0) 2020.06.15
[C#] Class, 클래스, 구조체  (0) 2020.06.08
[C#] 팝업창, 새로운 Form 열기, 모달vs모달리스  (0) 2020.06.01
[C#] 눈에 보이는 재미  (0) 2020.05.24
Posted by [ 브랜든 ]
,