2016. 10. 19. 07:07

.Net 3.5 이상부터 지원되는 Func<T, T Result> 를 사용하면 함수를 인자로 넘길 수 있습니다.


무슨 말인고 하니 함수를 콜 하였는데 그 함수 내부에서 다른 쪽의 함수를 실행해야 하는 경우가 있습니다.


이해를 돕기 위해 예문을 추가합니다.


using System;

namespace test1019
{
    class Program
    {
        static void Main(string[] args)
        {
            ClassA ca = new ClassA();

            ca.AA(BB); // 함수 BB 를 AA의 인자로 넘깁니다

            Console.WriteLine("Press any key to continue.");
            Console.ReadKey();
        }

        public static bool BB()
        {
            // do something...
            Console.WriteLine("BB");

            return true; // T Result 때문에 임의의 리턴값을 설정. 특별한 의미는 없다.
        }

        class ClassA
        {
            public void AA(Func<bool> func)
            {
                // do something
                Console.WriteLine("AA");

                func(); // AA 수행 중 클래스 바깥에 있는 BB()를 실행
            }
        }

    }

}

리턴타입이 void 형인 함수는 넘길수 없다는 것에 유의해 주세요.

Func<T, T Result> 가 인자 T는 생략이 가능하지만 T Result 인자는 생략이 안됩니다.




MSDN 링크 입니다.

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



Posted by 해비
2016. 9. 25. 17:25

프린터로 내용 출력하는 방법에 대해 다룹니다.


예시는 visualstudio 2015 Community edition 을 사용 합니다.

설치 시 SQL Server Data Tools 항목이 체크되어 있어야 합니다. 컨트롤이 보이지 않으면 프로그램 추가/제거 에서 변경을 누른다음 체크 하여 추가하도록 합니다.



도구상자 에서 ReportViewer 컨트롤을 폼에 추가 합니다.



새 항목 추가 - 데이터 집합 추가



데이터 집합 -> 추가 - DataTable



테이블 생성

테이블 명을 지정하고, Ctrl+L 을 눌러 컬럼을 하나씩 추가합니다. (Ctrl+L 누를때마다 컬럼이 하나씩 늘어납니다)



레포트 생성

새 항목 추가 - 보고서



보고서에 테이블 추가

이름은 적당히 알아서 지정하고, 데이터 원본과 데이터 집합은 위에서 생성한 데이터 집합의 내용을 선택합니다.



데이터 집합으로 부터 하나씩 끌어다 각 컬럼에 올립니다.

제목 출력을 위한 매개변수도 하나 추가, 배치 합니다.



작업한 보고서를 컨트롤에 연결

Report Viewer 선택 - 우상단 작은 삼각형 클릭 - 팝업에서 보고서 선택



소스코드

데이터 테이블 넘길때 이름을 위에서 지정한 데이터 집합에 설정한 이름으로 넘깁니다.

제목 파라메터 역시 레포트에서 생성한 파라메터 명칭으로 넘깁니다.

            DataTable dt = new DataTable();
            dt.Columns.Add("Name");
            dt.Columns.Add("Tel");
            dt.Columns.Add("Addr");

            dt.Rows.Add(new object[] { "AAA1", "BBB1", "CCC1" });
            dt.Rows.Add(new object[] { "AAA2", "BBB2", "CCC2" });
            dt.Rows.Add(new object[] { "AAA3", "BBB3", "CCC3" });

            for (int i = 10; i < 100; i++)
            {
                dt.Rows.Add(new object[] {
                    string.Format("AAA{0}", i.ToString())
                    , string.Format("BBB{0}", i.ToString())
                    , string.Format("CCC{0}", i.ToString()) });
            }

            ReportParameter[] rp = new ReportParameter[1];
            rp[0] = new ReportParameter("title", "샘플 보고서 제목");

            reportViewer1.PageCountMode = PageCountMode.Actual;

            ReportDataSource rDS = new ReportDataSource("reportdata", dt);
            this.reportViewer1.LocalReport.DataSources.Clear();
            this.reportViewer1.LocalReport.DataSources.Add(rDS);
            this.reportViewer1.LocalReport.SetParameters(rp);
            this.reportViewer1.LocalReport.Refresh();

            this.reportViewer1.RefreshReport();


빌드 및 실행




테이블이 2페이로 넘어가는 경우, 제목이 보여지지 않습니다.

페이지 마다 테이블 제목을 출력하도록 수정 하도록 하겠습니다.


레포트를 열고 테이블을 선택 합니다.

하단의 고급모드를 선택합니다.



행 그룹 항목의 정적을 클릭 -> 우측 속성에서 RepeatOnNewPage 값을 True 로 설정 합니다.



다시 빌드, 실행 하고 페이지를 넘겨보면 제목이 출력됨을 확인 할 수 있습니다.




본 레포트를 적용하여 개발한 프로그램 소스코드 입니다.

https://github.com/haebi/APTManager






Posted by 해비
2016. 1. 28. 21:58

엑셀 파일을 열고 데이터를 가져오는 것 까지 정리합니다.



절차는 크게 보면 아래 와 같습니다.

1. 파일열기

2. 시트목록 추출

3. 해당시트로 부터 데이터 추출




1. 파일열기

            openDlg1.FileName = "";

            // 변수선언

            string sFileName = "";

            OleDbConnection oleConn = new OleDbConnection();



            // 파일 읽기 다이얼로그 호출. 결과가 취소인 경우 여기서 실행을 멈춘다.

            if (this.openDlg1.ShowDialog() == DialogResult.Cancel)

            {

                return;

            }



            // 파일 읽기 다이얼로그로 부터 파일경로 획득

            if (!this.openDlg1.FileName.Equals(""))

            {

                sFileName = openDlg1.FileName;

            }

            else

            {

                return;

            }



            // xlsx와 xls의 연결 방법이 다르므로 구분하여 적용.

            if (sFileName.IndexOf("xlsx") >= 0)

            {

                // 여기서 오류 날 경우, 오피스 2007 시스템 드라이버를 설치한다.

                // https://www.microsoft.com/ko-kr/download/details.aspx?id=23734


                oleConn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0; Data Source=" + sFileName + @";Extended Properties=Excel 12.0";

            }

            else

            {

                oleConn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" + sFileName + "; Extended Properties=Excel 8.0;";

            }



            // 오픈~

            oleConn.Open(); 

- openfiledialog 를 사용하여 파일을 선택한다.

- 코드 내 주석에도 있지만, Open() 시 컨넥션 스트링 어쩌고 오류나는 경우가 있다. https://www.microsoft.com/ko-kr/download/details.aspx?id=23734 주소로 부터 시스템 드라이버를 다운받아 설치한다.




2. 시트목록 추출

            // 시트가 여러개 존재할 경우 모든 시트 목록을 만든다.

            DataTable sheetListDT = oleConn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new object[] { null, null, null, "TABLE" });

            string sheetName = "";


            // 시트 목록 돌면서 처리할 내용이 있다면 추가한다. (예: 특정 단어가 포함된 시트 제외 한다던가 뭐 그런 것들...)

            for (int i = 0; i < sheetListDT.Rows.Count; i++)

            {

                // 시트명 표시

                //sheetName = sheetListDT.Rows[i]["TABLE_NAME"].ToString();

                //MessageBox.Show(sheetName);


                // sheetListDT.Rows[i].Delete(); // 예: 시트 제거


            }


            // 변경 내용 반영

            sheetListDT.AcceptChanges(); 

- 시트 목록을 구한다. 일부 필요없는 시트가 있다면 여기서 걸러낸다.




3. 시트로 부터 데이터를 조회

            // 시트로 부터 데이터 조회

            //DataSet tmpDS = new DataSet();

            string sqlStr = "";

            for (int i = 0; i < sheetListDT.Rows.Count; i++)

            {

                sheetName = sheetListDT.Rows[i]["TABLE_NAME"].ToString();

                sqlStr = "SELECT * FROM [" + sheetName + "]";


                OleDbCommand oleCom = new OleDbCommand();

                oleCom.CommandType = CommandType.Text;

                oleCom.CommandText = sqlStr;

                oleCom.Connection = oleConn;


                OleDbDataAdapter oleAdt = new OleDbDataAdapter();


                oleAdt.SelectCommand = oleCom;


                DataTable tmpDT = new DataTable();


                oleAdt.Fill(tmpDT);


                tmpDT.TableName = sheetName;


                //tmpDS = StringMerge(tmpDS, tmpDT, 10);

                dataGridView1.DataSource = tmpDT;

            } 

- 테이터를 DataTable 에 채워 넣는다.

- dataGridView 컨트롤에 뿌리는 것으로 샘플코드는 종료~!!






결과 샘플


실행 예



사용된 엑셀 데이터







Posted by 해비
2015. 7. 2. 16:53

yyMMdd 형식(6자리)에서 yyyyMMdd 형식(8자리)으로 변환



string OrdYMD_RAW = "150702";
           
DateTime dtOrdYmd = DateTime.ParseExact(OrdYMD_RAW, "yyMMdd", null);
           
string strDate = dtOrdYmd.ToString("yyyyMMdd");
                       
// String --> DateTime   6자리 에서 DateTime 형식으로 변환.
// DateTime --> String   DateTime 형식에서 8자리 날짜 포맷으로 변환.



1. 1915 년에 컴퓨터가 없었다.

2. 2115년 까지 현재의 프로그램을 이용할 확률은 거의 없다.


위 2가지 생각에 비추어 편법으로 앞에 "20" + 6자리 날짜데이터(스트링) 으로 처리 할 수도 있었으나, 왠지 모르게 이건 아니다 싶어 변환 하고 다시 변환하는 방법으로 처리 했는데...


해놓고 보니 그게 그건가... 싶기도 -_-;;



Posted by 해비
2015. 5. 21. 22:04


텍스트 문장을 char 배열, 또는 byte 배열로 분해하면...? 문득 떠오른 것을 그냥 한번 만들어 보았다.


어디 써먹을 데 있으려나...





C# 에서 char는 문자 1개당 하나씩 대응(2바이트 인듯 하다) 하고, byte는 알고 있던대로... 한글 2바이트 기타 아스키 코드 1바이트 대응 하고 있다.




char 배열로 분해하기

char[] charArr = msgStr.ToCharArray(); 

- msgStr 은 문자열이 들어있는 string 변수이다.




byte 배열로 분해하기

System.Text.Encoding AscEnc = System.Text.ASCIIEncoding.GetEncoding("ks_c_5601-1987");

byte[] byteArr = AscEnc.GetBytes(msgStr);

- msgStr 은 문자열이 들어있는 string 변수이다.

- 문자열 인코딩을 설정한 다음 바이트 배열로 분해하여 담는다.





풀 소스코드는 Github 에서 다운로드 가능~

https://github.com/haebi/textsplitter




[실행파일]

textsplitter.zip


그냥 실행파일만 필요하면 이것을 받으면 된다. 

닷넷 4.5가 설치되어있지 않다면 실행되지 않고 불평을 늘어놓을 것이다. (아마도...)





Posted by 해비
2015. 4. 17. 15:07




요구사항:

예) 외출 시간이 10:00 ~ 14:00 인 경우, 원래 4시간 이나, 점심시간 (12:00 ~ 13:00) 이 포함되면 해당 범위만큼 제외한 시간을 산정하도록 한다.


입력은 분단위 환산된 정수 값이다.


한계)

- 당일 시간단위의 계산에 한함. 일자가 변경되는 것 까지 고려하지는 않는다.


        /* -----------------------------
         * 2015.04.17
         * Haebi, http://haebi.kr
         * ----------------------------- */
        /// <summary>
        /// 특정 시간간격을 제외한 총 분단위 간격을 구한다.
        /// </summary>
        /// <param name="fromMin">시작분</param>
        /// <param name="toMin">종료분</param>
        /// <param name="exFromMin">제외시작분</param>
        /// <param name="exToMin">제외종료분</param>
        /// <returns></returns>
        private int checkDurMinutes(int fromMin , int toMin, int exFromMin, int exToMin)
        {
            if (toMin < fromMin) throw new Exception("checkDurMinutes() : 종료시간이 시작시간보다 작을 수 없습니다.");
            if (exToMin < exFromMin) throw new Exception("checkDurMinutes() : 제외종료시간이 제외시작시간보다 작을 수 없습니다.");

            /**
             * 제외시킬 기간과 겹쳐질수 있는 4가지 케이스에 대하여 처리하고 있으며,
             * 해당없는 경우(겹쳐지지 않는경우) 그냥 to - from 처리로 끝난다.
             */
            int _retMin = 0;

            try
            {
                // -----<------|---------------|------>------
                if (fromMin <= exFromMin && toMin >= exToMin)
                {
                    _retMin = toMin - fromMin - (exToMin - exFromMin);
                }
                // ------------|---<------>----|-------------
                else if (fromMin >= exFromMin && toMin <= exToMin)
                {
                    _retMin = 0;
                }
                // ------<-----|---------->----|-------------
                else if ((fromMin <= exFromMin)
                    && (toMin >= exFromMin && toMin <= exToMin))
                {
                    _retMin = exFromMin - fromMin;
                }
                // ------------|----------<----|------>------
                else if ((fromMin >= exFromMin && fromMin <= exToMin)
                    && toMin >= exToMin)
                {
                    _retMin = toMin - exToMin;
                }
                // ---<----->--|---------------|-------------
                // ------------|---------------|---<----->---
                else
                {
                    _retMin = toMin - fromMin;
                }
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }

            return _retMin;
        }




그리고 검증을 위한 테스트 코드

               int min1 = 600; // 10:00
                int min2 = 630; // 10:30
                int dmin = 0;

                for (int i = 0; i < 300;i++)
                {
                    min2++; dmin = checkDurMinutes(min1, min2, 720, 780); printLog(i + 1, dmin, min1, min2);
                }



printLog() 부분

        private void printLog(int count, int dmin, int min1, int min2)
        {
            // 테스트 결과 출력 (단위:분)
            // 테스트 넘버    from ~ to 기간    from 분    to 분
            textBox3.Text = textBox3.Text + "\r\n" + count.ToString() + "\t"
                + string.Format("{0:D2}", (dmin / 60)) + ":" + string.Format("{0:D2}", (dmin % 60))
                + "\t" + string.Format("{0:D2}", (min1 / 60)) + ":" + string.Format("{0:D2}", (min1 % 60))
                + "\t" + string.Format("{0:D2}", (min2 / 60)) + ":" + string.Format("{0:D2}", (min2 % 60));
        }

- textBox3 하나 추가해 주면 된다.




테스트 결과(샘플)

카운트 - (외출)시간 - 시작시간 - 종료시간

88    01:58    10:00    11:58
89    01:59    10:00    11:59
90    02:00    10:00    12:00

91    02:00    10:00    12:01
92    02:00    10:00    12:02
93    02:00    10:00    12:03

...(생략)
148    02:00    10:00    12:58
149    02:00    10:00    12:59
150    02:00    10:00    13:00

151    02:01    10:00    13:01
152    02:02    10:00    13:02
153    02:03    10:00    13:03

- 1분단위로 종료시간을 더해가면서 실제 반영되는 시간이 얼마인지를 확인 한다.

- 테스트는 720(12:00) ~ 780(13:00) 까지의 범위에 대해 계산을 하지 않는 것으로 설정하였다.




Posted by 해비
2015. 4. 10. 09:41

Datatable 사용해서 처리하는 구문이 있었는데 의도하지 않은 방향으로 흘러가 버렸네요...


일단 의도는 다음과 같습니다.


1. 테이블에서 Select 하여 대상 데이터를 선택합니다.

2. 원 테이블을 클리어 시킵니다.

3. 선택된 데이터를 집어 넣습니다.



위 의도대로 아래와 같이 코드를 작성하였습니다

            // 테이블 정의
            DataTable srcDT = new DataTable();
            srcDT.Columns.Add("A1");
            srcDT.Columns.Add("A2");
            srcDT.Columns.Add("A3");
            srcDT.Columns.Add("A4");

            // 데이터 입력
            srcDT.Rows.Add(new object[] { "AAA", "111", "A11", "1AA" });
            srcDT.Rows.Add(new object[] { "BBB", "222", "B11", "1BB" });
            srcDT.Rows.Add(new object[] { "CCC", "333", "C11", "1CC" });

            // 커밋
            srcDT.AcceptChanges();

            // 쿼리
            DataRow[] fDR = srcDT.Select("A1 like '" + "A" + "%'");

            srcDT.Clear();
            foreach (DataRow dr in fDR)
            {
                srcDT.Rows.Add(dr);
            }

            for (int i = 0; i < srcDT.Rows.Count; i++)
            {
                textBox1.AppendText(srcDT.Rows[i][0].ToString() + " | "
                    + srcDT.Rows[i][1] + " | "
                    + srcDT.Rows[i][2] + " | "
                    + srcDT.Rows[i][3] + " | " + "\r\n");
            }


이 소스코드가 잘 돌아갈 것 같이 보입니까?



여기에는 치명적인 문제가 있습니다.

원 테이블을 클리어 시켜 버렸다는 것.


선택된 목록만을 가져오기 위해서 Clear 후 다시 선택된 값을 집어 넣고 있지만...;;


정작 빈 껍데기 row만 들어갑니다.


Select 할 때, 값을 복사해 오는것이 아니고 해당 데이터의 위치주소 값을 가져오는 듯 합니다.

그래서, 테이블 클리어 때 값이 다 지워졌고, 해당 주소값의 row를 다시 추가해 봤자 이미 다 지워진 빈 데이터 만 추가될 뿐이고...




이런 경우 제가 찾은 해결책은 2가지 입니다.


1번째는 목록을 not like 로 가져 온 다음, 해당 하는 행을 remove 로 지워 나가는 것.

2번째는 like 로 가져온 행을 새 테이블에 넣어주는 방법 (넣기 전에 원 테이블이 Clear 되면 안됩니다.)



1번째 소스코드

            // 쿼리
            DataRow[] fDR = srcDT.Select("A1 not like '" + "A%" + "%'");

            //srcDT.Clear();
            foreach (DataRow dr in fDR)
            {
                srcDT.Rows.Remove(dr);
            }

            for (int i = 0; i < srcDT.Rows.Count; i++)
            {
                textBox1.AppendText(srcDT.Rows[i][0].ToString() + " | "
                    + srcDT.Rows[i][1] + " | "
                    + srcDT.Rows[i][2] + " | "
                    + srcDT.Rows[i][3] + " | " + "\r\n");
            } 




2번째 소스코드

            // 쿼리
            DataRow[] fDR = srcDT.Select("A1 like '" + "A" + "%'");

            //srcDT.Clear();
            DataTable srcDT2 = new DataTable();
            srcDT2 = srcDT.Clone();
            foreach (DataRow dr in fDR)
            {
                srcDT2.Rows.Add(dr.ItemArray);
            }

            for (int i = 0; i < srcDT2.Rows.Count; i++)
            {
                textBox1.AppendText(srcDT2.Rows[i][0].ToString() + " | "
                    + srcDT2.Rows[i][1] + " | "
                    + srcDT2.Rows[i][2] + " | "
                    + srcDT2.Rows[i][3] + " | " + "\r\n");
            }






Posted by 해비
2015. 2. 24. 15:52

텍스트 상자 라던가...


여튼 입력 항목이 많을 때에는 컨트롤 배열로 선언해서 다루면 여러모로 편리합니다. ^__^

가끔 써먹어야 하는데 기억이 가물가물 해서 정리를...

 


전역변수 선언

        TextBox[] tBox; // 컨트롤 배열로 사용할 TextBox 전역변수 선언

        const int ARR_TEXTBOX_COUNT = 20; // 생성할 텍스트 박스 갯수 설정 



컨트롤 배열 생성, 기본 옵션 설정, 이벤트 연결

        private void CreateTextBoxes()

        {

            tBox = new TextBox[ARR_TEXTBOX_COUNT];


            for (int i = 0; i < tBox.Length; i++)

            {

                // 새 인스턴스 생성

                tBox[i] = new TextBox();


                // 기본옵션 설정

                tBox[i].Name = "txt" + i.ToString(); // 텍스트상자에 이름을 부여한다. 예) txt1

                tBox[i].Text = "";

                tBox[i].Width = 32;

                tBox[i].Height = 21;

                tBox[i].Tag = i.ToString();


                // 그룹박스에 텍스트상자 추가, 각 그룹박스당 10개씩 생성한다.

                if(i < 10)

                {

                    groupBox1.Controls.Add(tBox[i]);

                }

                else

                {

                    groupBox2.Controls.Add(tBox[i]);

                }


                // 이벤트 등록

                tBox[i].KeyPress += Form1_KeyPress;

            }



이벤트 처리

        // 키눌림 이벤트 처리

        void Form1_KeyPress(object sender, KeyPressEventArgs e)

        {

            // 엔터 키 입력

            if(e.KeyChar == (char)13)

            {

                // 이벤트가 발생한 컨트롤의 핸들 얻기

                TextBox hTextBox = (TextBox)sender;


                // 컨트롤 번호 얻기

                int ctrlNum = Convert.ToInt32(hTextBox.Tag);


                // 마지막 박스에서는 다음으로 이동하지 않는다. (오류방지)

                if(ctrlNum < ARR_TEXTBOX_COUNT -1)

                {

                    tBox[ctrlNum + 1].Focus();

                }

            }

        } 



텍스트 상자 이름 한번에 가져오기

        // 모든 텍스트 상자의 이름 가져오기!!

        private void getControlNames(Control control)

        {

            // 대상 control 의 하위에 있는 모든 컨트롤을 대상으로 스캔한다.

            foreach (Control tmpCtrl in control.Controls)

            {

                // 재귀호출 : 컨트롤이 또 다른 컨트롤을 품고 있으면 하위의 끝까지 스캔한다.

                if (tmpCtrl.Controls.Count > 0)

                    getControlNames(tmpCtrl);


                // 현재 스캔중인 컨트롤이 TextBox 형식이면...

                if (tmpCtrl is TextBox)

                {

                    TextBox textBox = (TextBox)tmpCtrl; // TextBox 으로 형 변환 한다.


                    txtLog.AppendText(textBox.Name.ToString() + "\n"); // 현재 컨트롤 이름 Log 추가

                }

            }

        }




vs2013 에서 작성한 예제 소스 첨부


ControlArrayExample.rar




예제 실행 화면



Posted by 해비
2014. 12. 3. 15:11



소스코드

using System.Runtime.InteropServices;


...

Some Class ...


        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
        static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

        private int WM_IME_NOTIFY         = 0x0282;
        private int WM_DESTROY            = 0x0002;
        private int WM_NCDESTROY          = 0x0082;
        private int WM_CLOSE              = 0x0010;
        private int IMN_CLOSESTATUSWINDOW = 0x0001;
        private int WM_KILLFOCUS          = 0x0008;
        private int WM_COMMAND            = 0x0011;


...


        protected override void WndProc(ref Message m)
        {
            if(m.Msg == (int)WM_CLOSE)
            {
                MessageBox.Show("X 눌렸엉 ㅠㅠ");
            }

            base.WndProc (ref m);
        }




Form1_Closing() 역시 종료 할때 발생합니다만, 그 이전에 WndProc 가 먼저 처리 됩니다.


X 눌렸을 때 막고, 닫기 버튼을 눌렀을 경우에만 종료 시키려면 Form1_Closing() 메서드에 e.Cancel = false; 주되, 특정 변수값 세팅이면 true 로 주면 되겠네요.


닫기 버튼은

변수 값 세팅, form.Close() 주면 될 것이고


우상단 X 는

WndProc에서 변수값 세팅해 주면 될 것이고~ 



그냥 Form1_Closing() 에 e.Cancel = false; 만 주면 불사속성을 지닌 창을 마주하게 될 겁니다. 





Posted by 해비
2014. 11. 28. 17:05


코드 실행 중 새 창 띄우고, 새 창이 닫히면 다시 코드가 계속 실행되는 예제 입니다.


요약 : 모달 폼으로 띄우면 됩니다.

            textBox1.AppendText("1");
            textBox1.AppendText("2");
            textBox1.AppendText("3");
           
            Form2 form2 = new Form2();
            form2.ShowDialog(this);
         
            textBox1.AppendText("4");
            textBox1.AppendText("5");
            textBox1.AppendText("6"); 


저렇게 다이얼로그 형식으로 불러내면 새 창 닫기 전 까지 코드 수행이 중단 됩니다.

Posted by 해비