천객만래 [千客萬來] (It has an interminable succession of visitors)



엑셀 팁 : 종속적인 드롭다운 목록 생성 방법

원문보기:
http://www.itworld.co.kr/news/109809#csidx12f15cb79a40e5eb63b71f243231480






데이터 탭에서 데이터 유효성 검사 > 데이터 유효성 검사

유효성 조건 섹션의 ‘제한 대상’의 드롭다운메뉴에서 ‘목록’ 옵션

lists 스프레드시트

의사에 해당되는 범위 선택




Posted by SB패밀리

엑셀에서 숫자를 문자로 NUMBERSTRING 함수 


word 문서




Excel_Tip_숫자를문자로_NUMBERSTRING_함수.zip


Posted by SB패밀리



엑셀 저장시 “공유 위반 때문에 변경 내용을 filename에 저장할 수 없습니다.

이런 에러 메시지 때문에 당황하신 적이 있어서 이 글을 읽는 것으로 보입니다.


이는 윈도우7 에서 대체로 발생하는데 백신 프로그램과 연관이 있습니다.


V3 계열이나 알약 등에서 실시간 검사를 하는 경우 해당 파일을 '공유위반 오류'로 설정하기 때문인데


아래와 같이 해결을 할 수 있습니다.


가장 많이 사용하는 V3 Lite를 예를 들어 V3 Lite을 실행 한 후 환경설정화면을 띄운 후 

아래와 같이 '검사 예외 설정'에서 '검사 예외 확장자' 입력을 하고 "확인" 버튼을 클릭합니다.




Posted by SB패밀리

[생활/문화] 나이 계산 수식 엑셀 


궁금하면 한 번 계산해 보자.






나이계산수식.xls



Posted by SB패밀리


DataSet에 있는 내용을 엑셀로 다운로드 하기 위해서 DataGrid를 사용합니다.
DataGrid 클래스를 생성하여 DataSet을 바인딩한 다음 엑셀에 보여질 스타일등을 지정해준 다음
해당 웹페이지에 Response 하면 됩니다.

 

private void Page_Load(object sender, System.EventArgs e)
{

    // 엑셀로 만들 데이터를 DataSet에 채웁니다.
    DataSet dsResult = GetMemberList();  

 

    // DataGrid와 DataTable의 각 필드에 상응하는 BoundColumn을 만들어 ArrayList에 추가합니다.

    ArrayList arrColumn = new ArrayList();
    arrColumn.Add(CreateBoundColumn("MEMID", "아이디"));
    arrColumn.Add(CreateBoundColumn("MEMNAME", "이름"));
    arrColumn.Add(CreateBoundColumn

            ("JOINDATE", "가입일", "{0:yyyy/MM/dd}", HorizontalAlign.Center));
    arrColumn.Add(CreateBoundColumn

            ("MILEAGE", "포인트", "{0:###,##0}", HorizontalAlign.Center));

 

    string fileName = "members_" + DateTime.Now.ToString("yyyyMMdd") + ".xls";

    SaveAsExcel(dsResult, arrColumn, "회원리스트", fileName);
}

 

public void SaveAsExcel(DataSet dsResult, ArrayList columns, string subject, string fileName)
{

    // 다운로드시 사용할 파일명을 설정합니다.
    if(fileName.Equals(null) && fileName.Equals(""))
        fileName = DateTime.Now.ToString("yyyyMMdd")+".xls";

 

    System.Web.HttpContext.Current.Response.Buffer = true;

 

    // DataGrid를 생성합니다.

    DataGrid dgExcel = new DataGrid();
    dgExcel.ShowHeader = true;

    dgExcel.Caption =  subject;

 

    // DataGrid에 Header 텍스트를 추가합니다.
    dgExcel.AutoGenerateColumns = false;

    foreach(object column in columns)
        dgExcel.Columns.Add((BoundColumn)column);

 

   // DataGrid의 스타일을 지정합니다.

    dgExcel.HeaderStyle.BackColor = Color.FromName("powderblue");
    dgExcel.HeaderStyle.HorizontalAlign = HorizontalAlign.Center;
    dgExcel.HeaderStyle.Height = 25;
    dgExcel.HeaderStyle.Font.Bold = true;

 

    // DataGrid에 DataSet의 내용을 채웁니다.

    dgExcel.DataSource = dsResult;
    dgExcel.DataBind();

 

    // DataGrid를 웹페이지에 쓰기 시작...

 

    System.Web.HttpContext.Current.Response.AddHeader
        ("Content-Disposition", string.Format("attachment;filename={0}", fileName));
    System.Web.HttpContext.Current.Response.ContentType = "application/unknown";
    // System.Web.HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";

 

    /////////////////////////////////////////////////////////////////////////////////////////////////
    ///  한글이 깨지는 경우 web.config의 globalization을 euc-kr로 바꿔주세요.
    /// <globalization requestEncoding="euc-kr" responseEncoding="euc-kr" />
    /////////////////////////////////////////////////////////////////////////////////////////////////

    System.Web.HttpContext.Current.Response.Write
        ("<meta http-equiv=Content-Type content='text/html; charset=ks_c_5601-1987'>");

 

    dgExcel.EnableViewState = false;


    System.IO.StringWriter sWriter = new System.IO.StringWriter();
    System.Web.UI.HtmlTextWriter htmlWriter = new System.Web.UI.HtmlTextWriter(sWriter);

    dgExcel.RenderControl(htmlWriter);

 

    System.Web.HttpContext.Current.Response.Write(sWriter.ToString());
    System.Web.HttpContext.Current.Response.End();


    dgExcel.Dispose();
}

 

// DataGrid의 반복적인 컬럼 생성 작업을 하는 메서드입니다.

public BoundColumn CreateBoundColumn(string DataFieldValue, string HeaderTextValue)
{
    // Create a BoundColumn.
    BoundColumn column = new BoundColumn();

 

    // Set the properties of the BoundColumn.
    column.DataField = DataFieldValue;
    column.HeaderText = HeaderTextValue;
    return column;
}

 

public BoundColumn CreateBoundColumn

   (string DataFieldValue, string HeaderTextValue, string FormatValue, HorizontalAlign AlignValue)
{
  
  // Create a BoundColumn using the overloaded CreateBoundColumn method.
    BoundColumn column = CreateBoundColumn(DataFieldValue, HeaderTextValue);

 

    // Set the properties of the BoundColumn.
    column.DataFormatString = FormatValue;
    column.ItemStyle.HorizontalAlign = AlignValue;
    return column;
}




쌈꼬쪼려 소백촌닭
Posted by SB패밀리

excel.php 라는 파일을 하나 만드시고 링크를 검색조건과 동일하게 변수와 값을 excel.php로 링크를 걸어주면 됩니다.

아래는 excel.php의 해더 만드는 부분입니다.

$filename = "excelfilename.xls";

header( "Content-type: application/vnd.ms-excel" );
header( "Content-Disposition: attachment; filename=$filename" );
header( "Content-Description: PHP4 Generated Data" );

이후 화면출력은 html화면 출력과 같이 html태그 사용으로 표만들어서 뿌려주면..

excel에서도 똑같이 보입니다 ^^

일반 목록 테이블처럼 table태그로 제목 달아주고 그 이후 tr,td태그로 내용 출력해주고 하면 알아서 뜹니다^^

html은 테스트 해보시면서 잘 꾸며보세요^^


- 쌈꼬쪼려 소백촌닭™ -

Posted by SB패밀리

예를 들어 게시판의 내용을 보여주는 파일명을 read.asp라고 하면 그 내용을 엑셀파일로 저장시킬 내용은 read_excel.asp라고 따로 만들고 read.asp에 read_excel.asp의 링크를 만듭니다.

그리고 read_excel.asp에 엑셀에 뿌려진 내용을 작성하신후 파일의 윗부분에 다음과 같이 구문을 적습니다.

Response.Buffer = TRUE
Response.ContentType = "application/vnd.ms-excel"
Response.AddHeader "Content-Disposition","attachment;filename=test.xls"


위의 구문을 파일 앞부분에 적으시면 브라우저에 표시되는 대신 엑셀 파일로 저장하는 저장대화상자가 뜹니다. 기본파일명은 test.xls로 되어 있는데 그 부분은 물론 수정가능합니다. 

- 쌈꼬쪼려 소백촌닭™ -
Posted by SB패밀리

기본적인 페이지 로드시 웹페이지가 아닌 엑셀시트임을 표시하는 것 만으로도

엑셀페이지 표시가 가능하다. 익스플로러를 이용해서 그런갑다...

 

        private void Page_Load(object sender, System.EventArgs e)

        {

            Response.ContentType = "application/vnd.ms-excel";

            Response.ContentEncoding = System.Text.UTF7Encoding.UTF7;

            string strReturn = "";

        }

 

2. DataGrid의 RenderControl를 이용해 엑셀로 저장하는 메서드를 보자..

데이터 그리드 내용만 저장된다.. 신기하죵?

 

        private void ImageButton1_Click(object sender, System.Web.UI.ImageClickEventArgs e)

        {

            string fileName = DateTime.Now.ToString("yyyyMMddHHmmss")+".xls";

            Response.ContentType = "application/vnd.ms-excel";

            Response.AddHeader("Content-Disposition","attachment;fileName="+fileName);

            Response.Charset = "";

            DataGrid1.EnableViewState = false;

            System.IO.StringWriter tw = new System.IO.StringWriter();

            tw.WriteLine("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=euc-kr\">");//한글 깨짐 방지

            System.Web.UI.HtmlTextWriter hw = new System.Web.UI.HtmlTextWriter(tw);

            DataGrid1.RenderControl(hw);

            Response.Write(tw.ToString());

            Response.End();

        }

 

3. 응용해서 어떠한 개체의 RenderControl를 이용해 엑셀로 저장 가능하다.

  일반 테이블로 만든 것에도 단순히... runat=server와 id를 부여하여 저장 가능하다.

  그라몬 서버 컨트롤로 인식 해부니까...

 다음의 예는 일반 테이블 테그에 서버컨트롤 지정해서 저장한다. 위에 데이터 그리드랑 별 차이 없다.

 

        private void btnExcel_Click(object sender, System.EventArgs e)

        {

            string fileName = "aa_"+DateTime.Now.ToString("yyyyMMddHHmmss")+".xls";

            Response.ContentType = "application/vnd.ms-excel";

            Response.AddHeader("Content-Disposition","attachment;fileName="+fileName);

            Response.Charset = "";

            System.IO.StringWriter tw = new System.IO.StringWriter();

            tw.WriteLine("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=euc-kr\">");//한글 깨짐 방지

 

            // 그래프도 함께 나도오록 하자..

            Chart1.HtmlTag = "Jpeg";

            Chart1.ImgWidth = 1000;

            tw.WriteLine(Chart1.GetHtmlTag(1000,450).Replace("IMG SRC=\"","IMG SRC=\"http://www.aaa.com")+"<br>");//그림을 붙이자.. 쩝쩝..

 

            System.Web.UI.HtmlTextWriter hw = new System.Web.UI.HtmlTextWriter(tw);

            this.tabResultList.RenderControl(hw);

            Response.Write(tw.ToString());

            Response.End();

        }


출처: http://www.devpia.com/blog/myblog.aspx?pdsidx=2304&ownerid=surfex

Posted by SB패밀리

엑셀 파일 다운로드/업로드(소스)

//  프로그램설명 : DB Table 데이터를 엑셀로 저장하여 다운로드 받을 수 있도록 하거나,


//  엑셀의 데이터를 DB Table로 업로드할 수 있는 클래스


//  단, Excel 프로그램이 설치되어있어야 동작 가능함. SqlHelper 부분은 별도로 추가하셔야 합니다.

// 주석을 자세히 달았으므로 이해하는데는 문제가 없으실 겁니다.


 

using System;
using System.IO;
using System.Data;
using System.Reflection;
using Excel=Microsoft.Office.Interop.Excel;
using System.Collections;
using System.Web;
using System.Web.UI;
using System.Data.OleDb;
using System.Data.SqlClient;

namespace Utilities
{
    ///


    /// DataTable을 입력 데이타로 받아서 해당 데이타로 새로운 엑셀 파일을 만들며
    /// 3가지 유형의 함수를 현재 제공하고 있다.
    /// 향후에는 ADO.NET 처리를 하는 부분과 엑셀 파일에 컬럼명이 추가되도록 할 예정이다.
    /// DB Table 데이터를 엑셀로 저장하여 다운로드 받을 수 있도록 하거나,
    /// 엑셀의 데이터를 DB Table로 업로드할 수 있는 클래스
    /// 단, Excel 2003 혹은 최소 XP 프로그램이 설치되어있어야 동작 가능함.
    ///

    public class ExcelHandler
    {
        ///
        /// 임시 생성 엑셀파일 목록 관리
        ///

        private static ArrayList exportedFiles = new ArrayList();
        ///
        /// 임시 파일 삭제 주기(분 단위)
        ///

        private static int deleteMinutePeriod = 30;

        ///


        /// 임시 엑셀 파일 삭제 주기 설정한다.
        /// 단위는 분단위임. 설정하지 않을 시에는 기본 값이 30분이다.
        /// 30분간만의 임시 파일만 유지한다.
        ///

        public static int DeletePeriodMinute
        {
            set
            {
                deleteMinutePeriod = value;
            }
            get
            {
                return deleteMinutePeriod;
            }
        }

        ///


        /// 생성자
        ///

        public ExcelHandler()
        {
        }

        #region 새로운 엑셀 파일 생성 유형
        ///


        /// 데이타 테이블을 입력 데이타로 받아서 해당 데이타로 새로운
        /// 엑셀 파일을 만든다.
        /// 저장된 엑셀 파일을 다운로드하지 않는다.
        /// 1번째 행과 1번째 열부터 저장한다.
        ///

        /// 입력 데이타(DataTable)
        /// 저장 경로명과 파일명
        /// 저장 성공 여부
        public static bool ExportNewFile(DataTable dt, string savedFile)
        {
            return PExportNewFile(dt, savedFile, 1, 1, null);
        }

        ///


        /// 데이타 테이블을 입력 데이타로 받아서 해당 데이타로 새로운
        /// 엑셀 파일을 만든다.
        /// 저장된 엑셀 파일을 다운로드하지 않는다.
        /// 1번째 행과 1번째 열부터 저장한다.
        /// 저장된 엑셀 파일을 다운로드한다.
        ///

        /// 입력 데이타(DataTable)
        /// 저장 경로명과 파일명
        /// 다운로드될 페이지
        /// 저장 성공 여부
        public static bool ExportNewFile(DataTable dt, string savedFile, Page downloadPage)
        {
            return PExportNewFile(dt, savedFile, 1, 1, downloadPage);
        }

        ///


        /// 데이타 테이블을 입력 데이타로 받아서 해당 데이타로 새로운
        /// 엑셀 파일을 만든다.
        /// 저장된 엑셀 파일을 다운로드하지 않는다.
        ///

        /// 입력 데이타(DataTable)
        /// 저장 경로명과 파일명
        /// 엑셀에서 데이타 시작행
        /// 엑셀에서 데이타 시작열
        /// 저장 성공 여부
        public static bool ExportNewFile(DataTable dt, string SavedFile, int row, int col)
        {
            return PExportNewFile(dt, SavedFile, row, col, null);
        }

        ///


        /// 데이타 테이블을 입력 데이타로 받아서 해당 데이타로 새로운
        /// 엑셀 파일을 만든다.
        /// 저장된 엑셀 파일을 다운로드한다.
        ///

        /// 입력 데이타(DataTable)
        /// 저장 경로명과 파일명
        /// 엑셀에서 데이타 시작행
        /// 엑셀에서 데이타 시작열
        /// 엑셀 파일 다운로드를 요청한 페이지
        /// 저장 성공 여부
        public static bool ExportNewFile(DataTable dt, string SavedFile, int row, int col, Page downloadPage)
        {
            return PExportNewFile(dt, SavedFile, row, col, downloadPage);
        }

        ///


        /// 데이타 테이블을 입력 데이타로 받아서 해당 데이타로 새로운
        /// 엑셀 파일을 만든다.
        ///

        /// 입력 데이타(DataTable)
        /// 저장 경로명과 파일명
        /// 엑셀에서 데이타 시작행
        /// 엑셀에서 데이타 시작열
        /// 엑셀 파일 다운로드를 요청한 페이지
        /// 저장 성공 여부
        private static bool PExportNewFile(DataTable dt, string SavedFile, int row, int col, Page downloadPage)
        {
            try
            {
                DeleteTemporaryFiles();

                GC.Collect();// clean up any other excel guys hangin'' around...
                Excel.Application excelApp = new Excel.Application();
                // excelApp.Visible = true;
                Excel.Workbook excelWorkbook = excelApp.Workbooks.Add(Type.Missing);
                Excel.Sheets excelSheets = excelWorkbook.Worksheets;
                string currentSheet = "Sheet1";
                Excel.Worksheet excelWorksheet = (Excel.Worksheet)excelSheets.get_Item(currentSheet);
    
                int ColCounter = dt.Columns.Count;

                // 컬럼명을 먼저 기록한다.
                int columnidex = 0;
                foreach(DataColumn dc in dt.Columns)
                {
                    excelWorksheet.Cells[row, col+columnidex++] = dc.ColumnName;
                }

                // 컬럼명을 적은 Row 다음에 데이터를 적을 수 있게끔 증가한다.
                row++;

                DataRow[] rpt = dt.Select();
                foreach(DataRow dr in rpt)
                {
                    for(int i = 0; i < ColCounter; i++)
                    {
                        excelWorksheet.Cells[row, col+i] = dr[i].ToString();
                    }
                    row++;
                }

                string tempSavedFileName = SavedFile + "." + Guid.NewGuid().ToString();
               

                FileInfo f = new FileInfo(tempSavedFileName);
                if(f.Exists)
                    f.Delete(); // delete the file if it already exist.
                // Workbook 저장
                excelWorkbook.SaveAs(tempSavedFileName, Excel.XlFileFormat.xlWorkbookNormal, null, null, false, false, Excel.XlSaveAsAccessMode.xlExclusive, false, false, null, null, null);
                // 클린업 작업
                excelWorkbook.Close(null, null, null);
                excelApp.Workbooks.Close();
                // excelApp.Application.Quit();
                excelApp.Quit();
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelWorksheet);
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelWorkbook);
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp);
                excelWorksheet = null;
                excelWorkbook = null;
                excelApp = null;
                GC.Collect();

                if(downloadPage != null)
                    Download(downloadPage, SavedFile, tempSavedFileName);

                return true;

            }
            catch( Exception theException )
            {
                GC.Collect();
                throw theException;
            }
        }
        #endregion

        #region 템플릿 파일 사용 유형 1
        ///


        /// 데이타 테이블을 입력 데이타로 받아서 해당 데이타로 기존 엑셀 파일(혹은 서식 파일)을
        /// 열어 엑셀 파일을 만든다.
        /// 저장된 파일의 다운로드를 요청하지 않는다.
        ///

        /// 입력 데이타
        /// 기존 엑셀 파일 경로명과 파일명
        /// 저장 경로명과 파일명
        /// 데이타가 저장될 Sheet명
        /// 저장 성공 여부
        public static bool ExportExistFile(DataTable dt, string OriginalFile, string SavedFile, string ws)
        {
            return PExportExistFile(dt, OriginalFile, SavedFile, ws, 1, 1, null);
        }

        ///


        /// 데이타 테이블을 입력 데이타로 받아서 해당 데이타로 기존 엑셀 파일(혹은 서식 파일)을
        /// 열어 엑셀 파일을 만든다.
        /// 저장된 파일의 다운로드를 요청한다.
        ///

        /// 입력 데이타
        /// 기존 엑셀 파일 경로명과 파일명
        /// 저장 경로명과 파일명
        /// 데이타가 저장될 Sheet명
        /// 엑셀 파일 다운로드를 요청한 페이지
        /// 저장 성공 여부
        public static bool ExportExistFile(DataTable dt, string OriginalFile, string SavedFile, string ws, Page downloadPage)
        {
            return PExportExistFile(dt, OriginalFile, SavedFile, ws, 1, 1, downloadPage);
        }

        ///


        /// 데이타 테이블을 입력 데이타로 받아서 해당 데이타로 기존 엑셀 파일(혹은 서식 파일)을
        /// 열어 엑셀 파일을 만든다.
        /// 저장된 파일의 다운로드를 요청하지 않는다.
        ///

        /// 입력 데이타
        /// 기존 엑셀 파일 경로명과 파일명
        /// 저장 경로명과 파일명
        /// 데이타가 저장될 Sheet명
        /// Sheet에서 데이타의 시작 행
        /// Sheet에서 데이타의 시작 컬럼
        /// 저장 성공 여부
        public static bool ExportExistFile(DataTable dt, string OriginalFile, string SavedFile, string ws, int row, int col)
        {
            return PExportExistFile(dt, OriginalFile, SavedFile, ws, row, col, null);
        }

        ///


        /// 데이타 테이블을 입력 데이타로 받아서 해당 데이타로 기존 엑셀 파일(혹은 서식 파일)을
        /// 열어 엑셀 파일을 만든다.
        /// 저장된 파일의 다운로드를 요청한다.
        ///

        /// 입력 데이타
        /// 기존 엑셀 파일 경로명과 파일명
        /// 저장 경로명과 파일명
        /// 데이타가 저장될 Sheet명
        /// Sheet에서 데이타의 시작 행
        /// Sheet에서 데이타의 시작 컬럼
        /// 엑셀 파일 다운로드를 요청한 페이지
        /// 저장 성공 여부
        public static bool ExportExistFile(DataTable dt, string OriginalFile, string SavedFile, string ws, int row, int col, Page downloadPage)
        {
            return PExportExistFile(dt, OriginalFile, SavedFile, ws, row, col, downloadPage);
        }

        ///


        /// 데이타 테이블을 입력 데이타로 받아서 해당 데이타로 기존 엑셀 파일(혹은 서식 파일)을
        /// 열어 엑셀 파일을 만든다.
        ///

        /// 입력 데이타
        /// 기존 엑셀 파일 경로명과 파일명
        /// 저장 경로명과 파일명
        /// 데이타가 저장될 Sheet명
        /// Sheet에서 데이타의 시작 행
        /// Sheet에서 데이타의 시작 컬럼
        /// 엑셀 파일 다운로드를 요청한 페이지
        /// 저장 성공 여부
        private static bool PExportExistFile(DataTable dt, string OriginalFile, string SavedFile, string ws, int row, int col, Page downloadPage)
        {
            try
            {
                DeleteTemporaryFiles();

                GC.Collect();// clean up any other excel guys hangin'' around...
                Excel.Application excelApp = new Excel.Application();
                Excel.Workbook excelWorkbook = excelApp.Workbooks.Open(OriginalFile, 0, false, 5, "", "", false, Excel.XlPlatform.xlWindows, "", true, false, 0, true, false, false);
                Excel.Sheets excelSheets = excelWorkbook.Worksheets;
                Excel.Worksheet excelWorksheet = (Excel.Worksheet)excelSheets.get_Item(ws);
                int ColCounter = dt.Columns.Count;

                // 컬럼명을 먼저 기록한다.
                int columnidex = 0;
                foreach(DataColumn dc in dt.Columns)
                {
                    excelWorksheet.Cells[row, col+columnidex++] = dc.ColumnName;
                }

                // 컬럼명을 적은 Row 다음에 데이터를 적을 수 있게끔 증가한다.
                row++;

                DataRow[] rpt = dt.Select();
                foreach(DataRow dr in rpt)
                {
                    for(int i = 0; i < ColCounter; i++)
                    {
                        excelWorksheet.Cells[row, i+col] = dr[i].ToString();
                    }
                    row++;
                }

                string tempSavedFileName = SavedFile + "." + Guid.NewGuid().ToString();

                FileInfo f = new FileInfo(tempSavedFileName);
                if(f.Exists)
                    f.Delete(); // delete the file if it already exist.
                // Workbook 저장
                excelWorkbook.SaveAs(tempSavedFileName, Excel.XlFileFormat.xlWorkbookNormal, null, null, false, false, Excel.XlSaveAsAccessMode.xlExclusive, false, false, null, null, null);
                // 클린업 작업
                excelWorkbook.Close(null, null, null);
                excelApp.Workbooks.Close();
                // excelApp.Application.Quit();
                excelApp.Quit();
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelWorksheet);
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelWorkbook);
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp);
                excelWorksheet = null;
                excelWorkbook = null;
                excelApp = null;
                GC.Collect();

                if(downloadPage != null)
                    Download(downloadPage, SavedFile, tempSavedFileName);

                return true;

            }
            catch( Exception theException )
            {
                GC.Collect();
                throw theException;
            }
        }
        #endregion

        #region 템플릿 파일 사용 유형 2
        ///


        /// Array를 입력 데이타로 받아서 해당 데이타로 기존 엑셀 파일(혹은 서식 파일)을 열어
        /// Array에 저장된 키, 값 형태의 데이터를 엑셀 파일 Sheet의 해당 위치에 저장되는 엑셀 파일을 만든다.
        /// 엑셀 파일을 만든 뒤 다운로드를 수행하지 않는 오버로드
        ///

        /// 입력 데이타(Array)
        /// 기존 엑셀 파일 경로명과 파일명
        /// 저장 경로명과 파일명
        /// 데이타가 저장될 Sheet명
        /// 저장 성공 여부
        public static bool ExportExistFile(Hashtable ht, string OriginalFile, string SavedFile, string ws)
        {
            return PExportExistFile(ht, OriginalFile, SavedFile, ws, null);
        }

        ///


        /// Array를 입력 데이타로 받아서 해당 데이타로 기존 엑셀 파일(혹은 서식 파일)을 열어
        /// Array에 저장된 키, 값 형태의 데이터를 엑셀 파일 Sheet의 해당 위치에 저장되는 엑셀 파일을 만든다.
        /// 엑셀 파일을 만든 뒤 다운로드를 수행하는 오버로드
        ///

        /// 입력 데이타(Array)
        /// 기존 엑셀 파일 경로명과 파일명
        /// 저장 경로명과 파일명
        /// 데이타가 저장될 Sheet명
        /// 엑셀 파일 다운로드를 요청한 페이지
        /// 저장 성공 여부
        public static bool ExportExistFile(Hashtable ht, string OriginalFile, string SavedFile, string ws, Page downloadPage)
        {
            return PExportExistFile(ht, OriginalFile, SavedFile, ws, downloadPage);
        }

        ///


        /// Array를 입력 데이타로 받아서 해당 데이타로 기존 엑셀 파일(혹은 서식 파일)을 열어
        /// Array에 저장된 키, 값 형태의 데이터를 엑셀 파일 Sheet의 해당 위치에 저장되는 엑셀 파일을 만든다.
        ///

        /// 입력 데이타(Array)
        /// 기존 엑셀 파일 경로명과 파일명
        /// 저장 경로명과 파일명
        /// 데이타가 저장될 Sheet명
        /// 엑셀 파일 다운로드를 요청한 페이지
        /// 저장 성공 여부
        private static bool PExportExistFile(Hashtable ht, string OriginalFile, string SavedFile, string ws, Page downloadPage)
        {
            try
            {
                GC.Collect();// clean up any other excel guys hangin'' around...
                Excel.Application excelApp = new Excel.Application();
                Excel.Workbook excelWorkbook = excelApp.Workbooks.Open(OriginalFile, 0, false, 5, "", "", false, Excel.XlPlatform.xlWindows, "", true, false, 0, true, false, false);
                Excel.Sheets excelSheets = excelWorkbook.Worksheets;
                Excel.Worksheet excelWorksheet = (Excel.Worksheet)excelSheets.get_Item(ws);

    
                foreach(string str in ht.Keys)
                {
                    int x = str.IndexOf(":");
                    string col = str.Substring(0, x);
                    string row = str.Substring(x + 1, str.Length - x -1);
                    excelWorksheet.Cells[row, col] = ht[str];
     
                }
    
                FileInfo f = new FileInfo(SavedFile);

                if(f.Exists)
                    f.Delete(); // delete the file if it already exist.
                // Workbook 저장
                excelWorkbook.SaveAs(SavedFile, Excel.XlFileFormat.xlWorkbookNormal, null, null, false, false, Excel.XlSaveAsAccessMode.xlExclusive, false, false, null, null, null);
                // 클린업 작업
                excelWorkbook.Close(null, null, null);
                excelApp.Workbooks.Close();
                // excelApp.Application.Quit();
                excelApp.Quit();
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelWorksheet);
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelWorkbook);
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp);
                excelWorksheet = null;
                excelWorkbook = null;
                excelApp = null;
                GC.Collect();

                if(downloadPage != null)
                {
                    Download(downloadPage, SavedFile);
                }

                return true;

            }
            catch( Exception theException )
            {
                GC.Collect();
                throw theException;
            }
        }
        #endregion

        #region 임시 생성 엑셀 파일 정리
        private static void DeleteTemporaryFiles()
        {
            // 임시로 생성한 엑셀 파일들 삭제
            if(exportedFiles.Count > 0)
            {
                ArrayList tempFiles = new ArrayList();

                foreach(FileInfo fi in exportedFiles)
                {
                    TimeSpan diffTimes = new TimeSpan( DateTime.Now.Ticks - fi.CreationTime.Ticks );

                    if(diffTimes.TotalMinutes > deleteMinutePeriod)
                    {
                        tempFiles.Add(fi);
                        fi.Delete();
                    }
                }
       
                foreach(FileInfo fi in tempFiles)
                    exportedFiles.Remove(fi);
            }
        }
       
        private static void AddExportedFileName(FileInfo fi)
        {
            exportedFiles.Add(fi);
        }
        #endregion

        #region 엑셀 파일 DB 업로드

        ///


        /// 엑셀로부터 데이터를 추출하여 DataSet으로 반환한다.
        ///

        /// 업로드할 엑셀 파일
        /// 엑셀 데이터셋
        public static DataSet GetDataSetFromExcelFile(System.Web.UI.HtmlControls.HtmlInputFile hif)
        {
            return GetDataSetFromExcelFile(hif, "Sheet1", true);
        }

        ///


        /// 엑셀로부터 데이터를 추출하여 DataSet으로 반환한다.
        ///

        /// 업로드할 엑셀 파일
        /// 엑셀 파일에 컬럼명 포함 여부(기본값: 포함)
        /// 엑셀 데이터셋
        public static DataSet GetDataSetFromExcelFile(System.Web.UI.HtmlControls.HtmlInputFile hif, bool IsIncludeHeader)
        {
            return GetDataSetFromExcelFile(hif, "Sheet1", IsIncludeHeader);
        }

        ///


        /// 엑셀로부터 데이터를 추출하여 DataSet으로 반환한다.
        ///

        /// 업로드할 엑셀 파일
        /// 업로드할 엑셀 파일내의 워크 쉬트명(기본값:Sheet1)
        /// 엑셀 파일에 컬럼명 포함 여부(기본값: 포함)
        /// 엑셀 데이터셋
        public static DataSet GetDataSetFromExcelFile(System.Web.UI.HtmlControls.HtmlInputFile hif, string WorkSheet, bool IsIncludeHeader)
        {
            string FilePath = Upload(hif);
            string ConnectionString1 = string.Empty;
            string excelConnStrFormat = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + FilePath + ";Extended Properties=\"Excel 8.0;HDR={0}\"";

            if(IsIncludeHeader == true)
                ConnectionString1 = string.Format(excelConnStrFormat, "YES");
            else
                ConnectionString1 = string.Format(excelConnStrFormat, "NO");

            OleDbConnection conn = new OleDbConnection(ConnectionString1);

            string strQuery;
            strQuery = "select * from [" + WorkSheet + "$]";
   
            OleDbCommand cmd = new OleDbCommand(strQuery, conn);
            OleDbDataAdapter Adapter = new OleDbDataAdapter();
            Adapter.SelectCommand = cmd;
            DataSet dsExcel = new DataSet();
            Adapter.Fill(dsExcel);

            return dsExcel;
        }

        ///


        /// 원하는 엑셀 파일의 원하는 워크쉬트를 지정한 DB의 테이블에 업로드한다.
        /// 이 경우 ht 파라미터를 통해 워크쉬트의 특정열을 테이블의 특정 컬럼에 매핑할 수 있다.
        /// 단, 이 경우 엑셀 파일의 컬럼에 대해서는 F1, F2, F3 ... 식으로 이름을 붙여야 한다.
        /// 그렇지 않고 Excel 커넥션 스트링의 HDR=YES로 설정하면 첫째 Row가 컬럼명으로 인식되게
        /// 할 수도 있다.
        ///

        /// 업로드할 엑셀 파일
        /// 업로드할 엑셀 파일내의 워크 쉬트명(기본값:Sheet1)
        /// 업로드할 DB 연결 문자열
        /// 업로드할 테이블명
        /// 엑셀 파일 컬럼명과 DB 테이블 컬럼명과의 매핑 정보
        /// 엑셀파일 DB업로드 성공 여부
        public static bool ExcelFileDBUpload(System.Web.UI.HtmlControls.HtmlInputFile hif, string WorkSheet, string ConnectionString, string Table, Hashtable ht)
        {
            return ExcelFileDBUpload(hif, WorkSheet, ConnectionString, Table, ht, true);
        }

        ///


        /// 원하는 엑셀 파일의 원하는 워크쉬트를 지정한 DB의 테이블에 업로드한다.
        /// 이 경우 ht 파라미터를 통해 워크쉬트의 특정열을 테이블의 특정 컬럼에 매핑할 수 있다.
        /// 단, 이 경우 엑셀 파일의 컬럼에 대해서는 F1, F2, F3 ... 식으로 이름을 붙여야 한다.
        /// 그렇지 않고 Excel 커넥션 스트링의 HDR=YES로 설정하면 첫째 Row가 컬럼명으로 인식되게
        /// 할 수도 있다.
        ///

        /// 업로드할 엑셀 파일
        /// 업로드할 DB 연결 문자열
        /// 업로드할 테이블명
        /// 엑셀 파일 컬럼명과 DB 테이블 컬럼명과의 매핑 정보
        /// 엑셀파일 DB업로드 성공 여부
        public static bool ExcelFileDBUpload(System.Web.UI.HtmlControls.HtmlInputFile hif, string ConnectionString, string Table, Hashtable ht)
        {
            return ExcelFileDBUpload(hif, "Sheet1", ConnectionString, Table, ht, true);
        }

        ///


        /// 원하는 엑셀 파일의 원하는 워크쉬트를 지정한 DB의 테이블에 업로드한다.
        /// 이 경우 ht 파라미터를 통해 워크쉬트의 특정열을 테이블의 특정 컬럼에 매핑할 수 있다.
        /// 단, 이 경우 엑셀 파일의 컬럼에 대해서는 F1, F2, F3 ... 식으로 이름을 붙여야 한다.
        /// 그렇지 않고 Excel 커넥션 스트링의 HDR=YES로 설정하면 첫째 Row가 컬럼명으로 인식되게
        /// 할 수도 있다.
        ///

        /// 업로드할 엑셀 파일
        /// 업로드할 DB 연결 문자열
        /// 업로드할 테이블명
        /// 엑셀 파일 컬럼명과 DB 테이블 컬럼명과의 매핑 정보
        /// 엑셀 파일에 컬럼명 포함 여부(기본값: 포함)
        /// 엑셀파일 DB업로드 성공 여부
        public static bool ExcelFileDBUpload(System.Web.UI.HtmlControls.HtmlInputFile hif, string ConnectionString, string Table, Hashtable ht, bool IsIncludeHeader)
        {
            return ExcelFileDBUpload(hif, "Sheet1", ConnectionString, Table, ht, IsIncludeHeader);
        }

        ///


        /// 원하는 엑셀 파일의 원하는 워크쉬트를 지정한 DB의 테이블에 업로드한다.
        /// 이 경우 ht 파라미터를 통해 워크쉬트의 특정열을 테이블의 특정 컬럼에 매핑할 수 있다.
        /// 단, 이 경우 엑셀 파일의 컬럼에 대해서는 F1, F2, F3 ... 식으로 이름을 붙여야 한다.
        /// 그렇지 않고 Excel 커넥션 스트링의 HDR=YES로 설정하면 첫째 Row가 컬럼명으로 인식되게
        /// 할 수도 있다.
        ///

        /// 업로드할 엑셀 파일
        /// 업로드할 엑셀 파일내의 워크 쉬트명(기본값:Sheet1)
        /// 업로드할 DB 연결 문자열
        /// 업로드할 테이블명
        /// 엑셀 파일 컬럼명과 DB 테이블 컬럼명과의 매핑 정보
        /// 엑셀 파일에 컬럼명 포함 여부(기본값: 포함)
        /// 엑셀파일 DB업로드 성공 여부
        public static bool ExcelFileDBUpload(System.Web.UI.HtmlControls.HtmlInputFile hif, string WorkSheet, string ConnectionString, string Table, Hashtable ht, bool IsIncludeHeader)
        {

            //   dsExcel = SqlHelper.ExecuteDataset(ConnectionString1, CommandType.Text, strQuery);
            //   SqlHelper.FillDataset(ConnectionString1, CommandType.Text, strQuery, dsExcel, Table);

            DataSet dsExcel = GetDataSetFromExcelFile(hif, WorkSheet, IsIncludeHeader);

            string ConnectionString2 = ConnectionString;
            SqlConnection conn2 = new SqlConnection(ConnectionString2);
            string strQuery2;
            strQuery2 = "select * from " + Table;
            SqlCommand cmd2 = new SqlCommand(strQuery2, conn2);
            SqlDataAdapter adpt = new SqlDataAdapter();
            adpt.SelectCommand = cmd2;
   
            SqlCommandBuilder custCB = new SqlCommandBuilder(adpt);

            DataSet dsDB = new DataSet();
            adpt.FillSchema(dsDB, SchemaType.Mapped);
   
            foreach(DataRow excelRow in dsExcel.Tables[0].Rows)
            {
                DataRow dbRow = dsDB.Tables[0].NewRow();
                foreach(string excelCol in ht.Keys)
                {
                    string DBCol = ht[excelCol].ToString();
                    dbRow[DBCol] = excelRow[excelCol];
                }
                dsDB.Tables[0].Rows.Add(dbRow);
            }

            DataSet dsUpload = dsDB.GetChanges();
            int result = adpt.Update(dsUpload);
            if(dsUpload.Tables[0].Rows.Count == result)
                return true;
            else
                return false;

        }
        #endregion
  
        #region 엑셀 파일 Download
        ///


        /// 생성된 엑셀 파일을 다운로드를 수행하는 로직
        ///

        /// 저장된 엑셀파일의 다운로드를 요청한 페이지
        /// 다운로드시 보여줄 엑셀파일 경로명과 파일명
        /// 서버에 저장된 임시 엑셀파일 경로명과 파일명
        private static void Download(Page downloadPage, string excelfile, string tempSavedFileName)
        {
            using(System.Web.UI.Page dp = downloadPage)
            {
                FileInfo fi = new FileInfo(tempSavedFileName);
                AddExportedFileName(fi);
                dp.Response.Clear();
                dp.Response.AddHeader("Content-Disposition", "attachment; filename=" + Path.GetFileName(excelfile));
                dp.Response.AddHeader("Content-Length", fi.Length.ToString());
                dp.Response.ContentType = "application/vnd.ms-exce";
                dp.Response.Charset = "";
                dp.EnableViewState = false;
                dp.Response.CacheControl = "public";
                dp.Response.WriteFile(fi.FullName);
                dp.Response.End();
            }
        }

        ///


        /// 생성된 엑셀 파일을 다운로드를 수행하는 로직
        ///

        /// 저장된 엑셀파일의 다운로드를 요청한 페이지
        /// 다운로드시 보여줄 엑셀파일 경로명과 파일명
        private static void Download(Page downloadPage, string excelfile)
        {
            using(System.Web.UI.Page dp = downloadPage)
            {
                FileInfo fi = new FileInfo(excelfile);
                dp.Response.Clear();
                dp.Response.AddHeader("Content-Disposition", "attachment; filename=" + Path.GetFileName(excelfile));
                dp.Response.AddHeader("Content-Length", fi.Length.ToString());
                dp.Response.ContentType = "application/vnd.ms-exce";
                dp.Response.Charset = "";
                dp.EnableViewState = false;
                dp.Response.CacheControl = "public";
                dp.Response.WriteFile(fi.FullName);
                dp.Response.End();
            }
        }
        #endregion

        #region 엑셀 파일 Upload
        private static string Upload(System.Web.UI.HtmlControls.HtmlInputFile hif)
        {

            if(hif.PostedFile.FileName.Length == 0)
            {
                throw new Exception("업로드할 파일이 없습니다.");
            }
            if(hif.PostedFile.ContentLength == 0)
            {
                throw new Exception("업로드할 파일의 크기가 0바이트입니다.");
            }

            string UploadPath = Path.GetTempPath();
   
            if(hif.PostedFile != null)
            {
                string fileName = Path.GetFileName(hif.PostedFile.FileName.ToString());
                string uniqueFilePath = GetUniqueFileNameWithPath(UploadPath, fileName);
                hif.PostedFile.SaveAs(uniqueFilePath);
                return uniqueFilePath;
            }
            else
            {
                throw new ApplicationException("업로드할 파일이 없습니다.");
            }
        }

        private static string GetUniqueFileNameWithPath(string dirPath, string fileN)
        {
            string fileName = fileN;

            int indexOfDot = fileName.LastIndexOf(".");
            string strName = fileName.Substring(0, indexOfDot);
            string strExt = fileName.Substring(indexOfDot+1);

            bool bExist = true;
            int fileCount = 0;

            while(bExist)
            {
                if(File.Exists(Path.Combine(dirPath, fileName)))
                {
                    fileCount++;
                    fileName = strName + "(" + fileCount + ")." +strExt;
                }
                else
                {
                    bExist = false;
                }
            }

            return Path.Combine(dirPath, fileName);
        }
        #endregion
    }
}

Posted by SB패밀리

Javascript로 Excel파일 만들어보자

테이블 같은 것들 중에 DataGrid나 DataList, Repeater가 아닌 경우 엑셀 다운로드를 붙여달라고 하면

가슴이 콱 답답해지시는 분들을 위한 간단한 코드입니다.

 

IE에서만 동작(엑셀이 windows 니까 ..ㅡㅡ;; 거의 IE겠죠 ..)

 

IFRAME 유동적으로 만들어서 거기다 해당 객체의 데이터를 넣고

해당 객체는 saveExcel('객체아이디') 로 넣으시면 됩니다.

파일명을 강제하실 경우 saveExcel('객체아이디','저장파일명.xls') 로 넣으시면 됩니다.

해당 IFRAME을 execCommand  이용해서 html 문서를 확장자 .xls로 저장합니다.

(저장 창이 header에서 excel 타입을 강제한거랑은 조금 다르게 생겨먹었습니다. ㅋㅋㅋ)

 

우야둔둥..이건 1원으로는 너무 싼듯해서..10원짜리 팁입니다. ㅋㅋ

 

아래는 소스와 예제입니다.

 <script language="javascript">

/*

암때나 맘때루 고쳐서 사용하셔도 됩니다.

대충 후려갈겼습니다 -_-;

CopyRight ⓒ Thisisx since 2006.11.30

*/

function saveExcel(targetId,SaveFileName)

{

    if(document.all)

    {

        if(!document.all.excelExportFrame) // 프레임이 없으면 만들자~!

        {

            var excelFrame=document.createElement("iframe");

            excelFrame.id="excelExportFrame";

            excelFrame.position="absolute";

            excelFrame.style.zIndex=-1;

            excelFrame.style.top="-10px";

            excelFrame.style.left="-10px";

            excelFrame.style.height="0px";

            excelFrame.style.width="0px";

            document.body.appendChild(excelFrame); // 아이프레임을 현재 문서에 쑤셔넣고..

        }

 

        var frmTarget = document.all.excelExportFrame.contentWindow.document; // 해당 아이프레임의 문서에 접근

 

        // content 타입은 넣으면 언젠가 MS에서 엑셀로 바로 열어주지 않을까해서.. 괜히 한번 선언해봤습니다.

        frmTarget.open("application/vnd.ms-excel","replace");

        frmTarget.write('<html>');

        frmTarget.write('<meta http-equiv=\"Content-Type\" content=\"application/vnd.ms-excel; charset=euc-kr\">\r\n'); // 별로..

        frmTarget.write('<body>');

        frmTarget.write(document.getElementById(targetId).outerHTML);  // tag를 포함한 데이터를 쑤셔넣고

        frmTarget.write('</body>');

        frmTarget.write('</html>');

        frmTarget.close();

        

        //frmTarget.charset="UTF-8"; // 자 코드셋을 원하는걸로 맞추시고..

        frmTarget.charset="euc-kr";

        frmTarget.focus();

        if(!SaveFileName)

        {

            SaveFileName='test.xls';

        }

        frmTarget.execCommand('SaveAs','false',SaveFileName); // 저장을 호출합니다.

    }

    else

    {

        alert('IE만 가능합니다.');

    }

}

</script>

 

 

<table id="tblMain">

<tr>

    <td>웅냐

    </td>

</tr>

</table>

 

<input type="button" value="excel" onclick="saveExcel('tblMain')">

Posted by SB패밀리

손쉬운 소프트웨어 스케줄 관리법


글 : Joel Spolsky
번역 :

AhnLab


2000년 3월 29일 수요일

지난해 10, 미국의 북동부 지역은 어딜 가나 아셀라(Acela) 광고 천지였다. 아셀라란 암트랙(Amtrak) 워싱턴-보스턴간 노선에서 운행할 새로운 초고속열차의 이름. 끊임없이 쏟아지는 TV 광고와 전광판 광고, 도처에 나붙은 포스터들을 보면서, 누구라도 정도의 물량 공세라면 새로운 초고속열차 서비스에 대한 수요 창출에 상당히 기여했으리라 생각했을 것이다.

글쎄, 그랬는지도 모르지. 하지만, 정작 암트랙사는 이를 확인해 기회가 없었다. 아셀라 프로젝트가 계속 지연되는 바람에 상용서비스가 개시되지도 않은 상태에서 광고 캠페인만 진행되고 있었던 것이다. 어떤 회사의 신제품 시판을 한달 앞두고 제품이 우수한 평가 등급을 받자 마케팅 책임자가 했다는 말이 떠오른다. “대단한 홍보 효과야! 이럴 시장에 물건만 있었으면 얼마나 좋았을까!”

남성호르몬이 철철 넘치는 게임 업체들은 자사 웹사이트에 제품 개발이 완료되는 대로 새로운 게임이 출시될 것이라고 떠들어대기를 좋아한다. 스케줄? 스케줄 같은 필요하지 않다. 그들은 나는 게임 개발자들이니까! 하지만, 대부분의 평범한 소프트웨어 업체들에게는 스케줄이 필요하다. 로터스의 경우가 좋은 예로, 로터스사가 처음 123 버전 3.0 개발을 완료했을 소프트웨어는 당시만 해도 아직 보급 대수가 많지 않던 80286 기종의 컴퓨터를 필요로 했다. 로터스는 제품 출시를 16개월이나 미뤘고 기간 동안 8086 기종의 메모리 한계인 640K 맞춰 제품의 용량을 줄였다. 수정된 제품이 완성되었을 때엔 이미 엑셀을 시판중인 마이크로소프트사가 시장에서 16개월 정도 앞서나가고 있었으며, 무슨 가혹한 운명의 장난인지, 8086 기종은 이미 시장에서 폐물 취급을 받고 있었다!

글을 쓰고 있는 지금도 넷스케이프의 인터넷 브라우저 5.0 경쟁사에 비해 거의 2가까이뒤쳐져있다. 개발된 코드를 모두 폐기하고 처음부터 코딩을 다시 시작하는 치명적인 실수를 저질렀기 때문이다. 애시톤-테이트, 로터스, 애플사의 MacOS 등도 똑같은 실수를 저지르는 바람에 소프트웨어 역사의 휴지통(Recycle-bin) 속으로 내던져지는 신세가 되고 말았다. 사이 넷스케이프사의 브라우저의 시장점유율은 80%대에서 20%대로 곤두박질쳤지만 회사는 아무것도 없었다. 주력 소프트웨어 제품이 산산조각으로 분해되어 있는 상태에서는 달리 손을 있는 방도가 없었기 때문이다. 한번의 잘못된 결정이 넷스케이프를 자폭시킨 핵폭탄이 셈이었다. ( 자세한 내용은 Jamie Zawinski world-famous tantrum 참조한다).

그래서, 스케줄관리가필요한것이다. 거의 모든 프로그래머들이 하기 싫어하는 일이 바로 스케줄 관리다. 필자의 경험에 따르면, 대다수의 프로그래머들은 어떻게 해서든 스케줄 같은 아예 짜지 않고 버텨 보려고 든다. 스케줄을 짜는 극소수의 프로그래머들도 대부분 상사가 하라고 하니까 억지로 하고 있을 , 스케줄대로 프로젝트가 진행되리라고 믿는 사람은 상사 밖에 없다. 상사는 스케줄을 믿는 한편으로, “기한 내에 완료되는 소프트웨어 프로젝트는 없다고도 믿으며 , UFO 존재도 믿는 사람이다.

그렇다면, 아무도 스케줄을 만들려 하지 않을까? 주된 이유는 가지다. 첫째는 스케줄을 짜고 관리하는 것이 매우 골치 아픈 일이기 때문이고, 번째 이유는 아무도 그것이 그만한 수고의 가치가 있는 일이라고 믿지 않기 때문이다. 스케줄이 지켜지지도 않을 거라면 도대체 그런 수고를 해야 한단 말인가? 스케줄이란 애당초 지켜지는 법이 없고 시간이 지날수록 현실과의 괴리는 점점 커지기만 하는데, 하러 그런 헛수고를 한단 말인가?

여기, 정확하게지켜지는스케줄을만드는간단하고손쉬운방법이있다.

1) 엑셀프로그램을사용하라. MS Project 같은 거창한 프로그램을 사용하려 들지 말라. MS Project 사용자가 종속성의 문제를 해결하기 위해 많은 시간을 할애할 것이라는 가정하에 만들어진 프로그램이다. 종속성이란, 가지 작업을 진행해야 하는 상황에서 하나의 선행 작업이 먼저 완료된 후에만 다음 작업을 시작할 있는 경우를 말한다. 소프트웨어 프로젝트의 경우, 작업들간의 종속성은 너무나 당연한 것이기 때문에 굳이 수고스럽게 종속성을 추적할 필요가 없다.

MS Project 사용하는 경우에 생길 있는 다른 문제는 프로그램을 사용하다 보면 자꾸 스케줄 균형 조정 기능을 실행하고 싶어진다는 것이다. 스케줄 균형 조정에는 불가피하게 모든 작업을 재조정하여 다른 사람에게 재할당하는 과정이 포함되는데, 소프트웨어 프로젝트의 경우 이런 재조정은 말이 되지 않는다. 프로그래머들간의 호환이 불가능하기 때문이다. 가령, 리타가 작업한 코드의 버그를 존이 수정하려면 리타 자신이 수정할 때보다 시간이 일곱 배는 걸린다. , UI 담당 프로그래머에게 WinSock 관련된 문제를 해결하라고 시키면, WinSock 프로그래밍에 익숙해지는 데에만 일주일은 걸릴 것이다. 결론은 MS Project 소프트웨어 개발 프로젝트보다는 건축 공사 프로젝트에 적합한 프로그램이라는 것이다.

2) 단순하게만들라. 필자가 이용하는 스케줄 포맷은 너무나 단순해서 한번만 봐도 기억할 있을 정도다. (컬럼) 개수는 일곱 개면 된다.

개발자가 여러 명인 경우에는 개발자별로 시트를 각기 따로 만들 수도 있고, 아니면 작업을 담당하는 개발자의 이름으로 컬럼을 만들 수도 있다.

3) 하나의기능은여러개의작업으로분해하라. 여기서, 기능이란 프로그램에 추가되는 맞춤법 검사 기능 같은 것을 말한다. 실제로 맞춤법 검사 기능을 추가하려면, 프로그래머는 여러 단계의 작업을 수행하게 된다. 스케줄을 가장 중요한 부분은 바로 이와 같은 구체적인 작업들의 목록을 만드는 것이다. 따라서 다음 규칙을 명심해야 한다.

4) 코딩을직접담당할프로그래머만이스케줄을만들있다. 관리자가 임의로 만든 스케줄을 프로그래머에게 던져주고는 따르라고 말하는 프로젝트는 실패할 밖에 없다. 코딩을 직접 담당할 프로그래머만이 기능을 구현하기 위해 어떤 작업들이 필요한가를 구체적으로 예측할 있고 작업에 어느 정도의 시간이 소요될 것인가도 추정할 있다.

5) 작업단위를세분화하라. 실효성 있는 스케줄을 만들기 위해 반드시 유념해야 부분이다. 작업량은 날짜가 아니라 시간 단위로 계산해야 한다. ( , 또는 단위로 작업 기간이 계산되어 있는 스케줄은 처음부터 지킬 생각으로 만든 스케줄이 아니다). ‘작업 단위를 잘게 쪼개면 정밀한 스케줄이 나오겠지 정도로 생각하는 독자가 있을지도 모른다. 천만의 말씀! 대략적인 작업 개요를 바탕으로 스케줄을 세워보고, 다시 작업 단위를 보다 세분화하여 스케줄을 세워보면, 단순히 정밀한 스케줄이 얻어지는 것이 아니라 전혀다른결과 나온다. 완전히 다른 수치가 나오는 것이다. 어째서 이런 일이 생기는 걸까?

작업 단위를 세분화하기 위해서는 실제 코딩 과정에서 구체적으로 어떤 일들을 수행하게 것인가를 생각해보지 않으면 된다. 서브루틴을 저작하고, 이러저러한 대화상자들을 만들고, wawa 파일을 읽어내고 등등. 같은 각각의 단계에 소요되는 시간을 추정하는 것은 어렵지 않다. 프로그래머라면 서브루틴을 저작하고 대화상자를 만들고 wawa 파일을 읽어내는 작업들은 모두 해봤을 것이기 때문이다.

만일 어떤 스케줄이 적당히 얼버무린 덩어리 작업들로만 (가령, “문법 검사 기능 구현하기 ) 채워져 있다면, 이는 프로그래머가 구체적으로 어떤 일들을 하게 것인가에 대해 제대로 생각해보지 않았다는 증거다. 그리고, 어떤 일들을 하게 것인지 제대로 생각해보지 않았다면, 거기에 어느 정도의 시간이 소요될 것인지도 없다.

개별 작업에는 어림잡아 2시간에서 많게는 16시간 정도까지가 소요된다. 스케줄에 40시간(1주일)짜리 작업이 포함되어 있다면, 작업 단위를 충분히 세분화하지 않았다는 이야기다.

스케줄을 작업 단위를 세분화해야 하는 다른 이유는 그런 과정을 통해 해당 기능을 머리 속으로 설계해보게 된다는 것이다. 만일 인터넷 통합이라는 떨리는 기능에 대한 스케줄을 수립하면서 무턱대고 3주일을 배정했다면 프로젝트는 실패할 밖에 없다. 구체적으로 어떤서브루틴들을만들어내야것인가 생각해내다 보면, 기능을 명확하게규정하게 된다. 같은 단계에서 미리 계획을 세워봄으로써 소프트웨어 프로젝트와 관련된 불안정성을 크게 줄일 있다.

 

6) 최초추정시간과현재추정시간을지속적으로추적하라. 어떤 작업을 처음 스케줄에 추가할 때는 작업에 어느 정도의 시간이 소요될 것인가를 추산한 , 결과를 Orig Est (최초 추정시간) 컬럼과 Curr Est (현재 추정시간) 컬럼에 모두 입력한다. 시간이 흐름에 따라, 작업 진도가 당초의 예정보다 지연 (혹은 단축)되면, 현재 추정시간 컬럼을 필요에 따라 업데이트할 있다. 같은 방식으로 관리하다 보면, 특정 작업에 필요한 시간을 보다 정확하게 추정하는 방법을 스스로 터득할 있다. 대부분의 프로그래머들은 각각의 작업에 어느 정도의 시간이 소요되는지 모른다. 그래도 걱정할 필요는 없다. 스케줄을 지속적으로 관리하고 그에 따라 업데이트하는 , 결국 스케줄과 프로젝트 완료 시기는 일치하게 된다. (최종 납기를 맞추기 위해 일부 기능이나 항목을 잘라버려야만 하는 경우도 생길 있지만, 그와 같은 추려내기가 필요한 시점이 언제인가를 일관되게 보여준다는 점에서 스케줄의 정확성은 여전히 유지될 것이다). 필자의 경험으로 , 대부분의 프로그래머들은 일년 정도만 연습하면 스케줄 짜는 도사가 된다.

작업이 완료되면, 현재 추정시간 컬럼과 Elapsed (경과시간) 컬럼의 값이 똑같아지고 Remain (잔여시간) 컬럼의 값은 0 된다.

7) 경과시간을매일업데이트하라. 물론, 스톱워치를 들여다보며 코딩을 진행할 필요는 없다. 퇴근하기 직전에, 혹은 회사에서 밤을 새워가며 일하는 경우라면 책상 밑에 기어들어가 잠깐 눈을 붙이기 직전에, 하루에 한번만 경과시간을 업데이트하면 된다. 8시간 동안 근무했다고 가정하고 (하하!), 그날 진행한 작업들에 대한 경과시간 필드에 8시간을 쪼개서 입력한다. 잔여시간 컬럼의 값은 엑셀이 자동으로 계산해 것이다.

그와 동시에, 현재 추정시간 컬럼도 업데이트하여 변화된 현실을 반영한다. 스케줄을매일업데이트하는걸리는시간은고작2정도밖에되지않는다. 그래서 방법을 손쉬운 스케줄 관리법이라고 부르는 것이다. 빠르고 간편하기 때문에.

8) 휴가기간과휴일등도항목에포함시켜라. 1 정도 진행되는 스케줄이라면, 사이에 프로그래머들의 휴가일수가 대개 10일에서 15 정도 포함될 것이다. 스케줄의 기능 컬럼에는 휴가, 휴일 기타 시간을 잡아먹는 모든 것들에 대한 항목이 전부 포함되어야 한다. 잔여시간 컬럼의 값을 모두 더한 이를 40(1주일)으로 나누면, 휴가 모든 요소를 포함하여 작업 기간이 주일이나 남아있는지를 산출할 있고 제품 출시 일자도 계산할 있다.

9) 디버깅시간도고려하라! 디버깅은 소요시간을 추정하기가 가장 어려운 항목이다. 가장 최근에 작업했던 프로젝트를 떠올려보라. 아마도 코딩 단계에서 소요된 시간의 100% 내지 200% 해당하는 시간이 디버깅에 소요되었을 것이다. 스케줄의 기능 컬럼에는 반드시 디버깅 항목이 포함되어야 하며, 아마도 가장 값이 항목이 것이다.

스케줄 업데이트 방법은 다음과 같다. 가령, 어떤 개발자가 wawa 파일을 코딩한다고 하자. 처음 스케줄을 예상했던 최초 추정시간은 16시간이었지만, 현재까지 이미 20시간이 소요되었고 앞으로도 10시간 정도는 걸릴 것으로 예상된다. 경우, 개발자는 현재 추정시간 컬럼에 30 입력하고 경과시간 컬럼에 20 입력하면 된다.

주요 작업 단계가 완료된 , 모든 항목들의 값들을 더해보니 상당한 수치가 나왔다. 이론상으로 제품 납기를 맞추자면 몇몇 기능을 잘라내 버려야 한다. 이렇게 시간에 쫓길 프로그래머들이 가장 먼저 잘라낼 있는 기능이 바로 디버깅 항목이다. 다행히도, 처음 스케줄을 디버깅 항목에 많은 시간을 할당해 두었으니까.

원칙적으로, 개발자들은 코딩과 동시에 디버깅을 병행해야 한다. 절대로, 수정할 있는 버그를 뒤로 미뤄둔 새로운 코딩에 착수해서는 된다. 미해결 버그의 수는 가능한 최소한으로 유지해야 한다. 이유는 가지다.

1) 버그는 코딩 직후에 곧바로 수정하는 것이 가장 수월하다. 달쯤 지난 후에 버그를 수정하려고 하면, 정확한 코딩 방식을 기억하지 못하기 때문에 버그 수정도 어려워지고 시간도 많이 걸릴 있다.

2) 버그 수정은 과학적 연구와 비슷해서, 언제 새로운 과학적 사실을 발견할 있을지 정확히 예측하기 어려운 것처럼 언제 버그를 해결할 있을지 정확히 예측하는 것도 쉽지 않다. 수정해야 버그가 개나 개뿐이라면 제품 출시 시기를 예측하는 것은 어렵지 않겠지만, 미해결 버그가 수백 개나 수천 개쯤 되는 경우에는 이것들을 언제 전부 수정할 있을지 예측하기란 불가능하다.

그렇다면, 개발자가 코딩 과정에서 버그를 발견할 때마다 곧바로 버그를 수정하는데 디버깅 스케줄을 따로 만들어야 하는가? 프로그래머가 모든 버그를 그때그때 수정한다 하더라도, 작업 단계가 완료되면 불가피하게 버그 수정 과정이 필요하다. 테스트 (내부 테스트 베타 테스트) 단계에서 정말로 어려운 버그들이 발견되기 때문이다.

10) 통합시간도고려하라. 여러 명의 프로그래머가 함께 작업하는 경우, 프로그래머들 간에 서로 일치되지 않아 조정이 필요한 부분이 생기게 마련이다. 가령, 유사한 기능의 대화상자를 명의 프로그래머가 서로 다른 방식으로 구현할 수도 있다. , 여러 명의 프로그래머들이 각자 마음대로 추가한 메뉴 항목이나 키보드 단축키, 툴바 등을 누군가는 전체적으로 정리하고 통일시켜야만 한다. 사람이 코드에 체크인하는 순간 컴파일러 에러가 발생할 수도 있다. 모든 것들을 수정할 시간이 필요하며, 이는 별도의 기능 항목으로 스케줄에 포함시켜야 한다.

11) 여분의시간을포함시켜라. 시간은 모자라게 마련이다. 스케줄을 고려해야 여분의 시간(버퍼) 가지 종류가 있다. 첫째는 당초 추정했던 것보다 시간이 많이 걸리는 작업을 고려한 버퍼이고, 둘째는 관리자가 wawa 구현이 너무나도 중요해서 다음 버전으로 미룰 없다고 결정하는 경우 등과 같이, 계획에 없이 갑자기 추가된 작업을 고려한 버퍼다.

혹시라도 휴가, 휴일, 디버깅, 통합 시간 버퍼 항목 등을 모두 더한 값이 실제 작업 시간을 모두 더한 값보다 크다는 사실을 알고는 놀랄 수도 있다. 이런 사실에 놀라는 독자라면 아마 프로그래밍 경력이 아직 그리 길지 않은 개발자일 것이다. 책임질 자신이 있거든, 이런 항목들을 빼버려도 좋다.

12) 절대로관리자가프로그래머에게스케줄단축을요구해서는된다. 풋내기 소프트웨어 관리자 중에는 프로그래머들에게 빡빡한” (비현실적으로 짧은) 스케줄을 던져주는 방법으로 그들을 자극하여 작업을 보다 빨리 진행시킬 있다고 생각하는 사람들이 많다. 필자의 생각으로는 이런 식의 자극은 멍청한 짓일 뿐이다. 필자의 경우, 스케줄에 뒤쳐지면 오히려 좌절감이 생기고 의욕이 저하되며, 스케줄보다 진도가 빠를 활기도 생기고 생산성도 높아진다. 관리자는 스케줄을 심리전의 도구로 이용해서는 된다.

그럼에도 불구하고, 관리자가 스케줄 단축을 요구하는 경우에는 이렇게 대처하라. 릭의추정시간이라는 새로운 컬럼을 하나 추가한다 (물론, 프로그래머의 이름이 릭이라고 가정하고.) 프로그래머 자신이 예상하는 추정시간을 컬럼에 입력한다. 관리자가 원하는 새로운 스케줄은 현재추정시간 컬럼에 업데이트 한다. 관리자의 추정시간은 무시하고 작업을 진행하라. 프로젝트가 완료된 후에 누구의 스케줄이 현실적이었는지 비교해 보라. 필자의 경험에 따르면, “누구 스케줄이 맞는지 한번 볼까요?” 라고 관리자를 위협하는것만으로도 놀랄만한 효과가 있다. 자칫하면 프로그래머들과 얼마나 느릿느릿 일할 있는지 두고 보자는 식의 내기를 하는 꼴이 되고 만다는 사실을 관리자도 깨닫게 테니까!

풋내기 관리자들은 프로그래머의 스케줄을 단축하려고 노력하는가?

프로젝트가 처음 시작되면, 기술 관리자들은 비즈니스 담당자들과 회의를 하고는 그들이 3개월 정도 소요될 것으로 생각하는, 하지만 실제로는 9개월이 걸리는 제품 기능들이 적힌 목록을 만들어 온다. 어떤 작업들을 수행해야 하는가를 구체적으로 생각해 보지 않고 스케줄을 짜는 경우, 항상 실제로는 3n 시간 이상 걸릴 일이n 시간 정도 걸릴만한 일인 것처럼 보이게 된다. 현실적인 스케줄을 짜다 보면, 모든 작업 시간을 전부 더하게 되고 프로젝트가 당초 생각했던 것보다 훨씬 시간이 많이 걸릴 것이라는 사실을 깨닫게 된다. 현실은 가혹한 것이다.

풋내기 관리자들은 같은 문제를 해결하기 위해, 사람들이 보다 빨리 일하도록 만들 있는 방안을 찾아내려 애쓴다. 하지만, 이런 식의 접근은 그다지 현실적인 방법이 아니다. 작업 인원을 늘릴 수는 있겠지만, 그렇더라도 신입 프로그래머들이 작업에 익숙해질 때까지 상당한 시간이 필요하기 때문에 정도는 작업 효율이 50% 정도 밖에 되지 않을 것이다 (게다가, 그들에게 일을 가르쳐야 하는 사람들의 작업 효율까지 떨어진다). 무엇보다, 소프트웨어 업계에서 쓸만한 프로그래머를 새로 뽑자면 6개월 정도는 시간이 걸린다.

모든 에너지가 일년 안에 100% 소진될 만큼 팀원들을 쥐어짠다면 혹시 일시적으로 코드 산출량이 10% 정도 늘어날지도 모른다. 하지만, 별로 남는 장사는 아니다. 무엇보다, 이것은 배가 고프다고 종자용 옥수수까지 먹어치우는 것과 비슷한 꼴이다.

모든 사람들에게 아무리 피곤하더라도 참고 초인적으로 열심히 일해달라고 애원한다면 혹시 코드 산출량이 20% 정도 늘어날 있을지도 모른다. 하지만, 디버깅 시간은 배로 늘어날 것이다. 이야말로 숙명적으로 실패가 예정된 악수(惡手) 있다.

하지만, 아무리 해도 절대로 n 가지고 3n 이룰 수는 없다. 만일 있다고 믿는 회사가 있다면, 필자에게 이메일로 회사의 나스닥 상장기호를 일러주기 바란다. 필자가 상장기호를 반으로 줄여줄 테니까.

13) 스케줄은나무블록과같다. 집짓기 놀이용 나무 블록이 꾸러미 있는데 이걸 주어진 상자에 전부 담을 수가 없다면, 방법은 가지다. 상자를 구해오거나, 아니면 나무 블록 중에서 개는 포기하는 것이다. 6개월이면 제품을 완성할 있을 거라고 생각했었는데 작업을 진행하다 보니 12개월짜리 스케줄이 됐다면, 제품 출시를 늦추든가 아니면 몇몇 기능을 포기해야 한다. 나무 블록이 작아지게 만들 수는 없으니까. 만일 작아지게 만들 있는 것처럼 행동한다면, 이는 지금 앞에 보이는 것에 대해 스스로에게 거짓말을 함으로써 장래를 예측할 있는 기회마저 스스로 박탈하는 일이다.

이와 같은 스케줄 관리를 통해 얻을 있는 다른 부수적 효과는 많은 기능들 중에서 일부를 포기하게끔 만든다는 것이다. 포기하게끔 만드는 것이 좋으냐고? 여기 가지의 기능이 있다고 가정하자. 하나는 진짜로 쓸모 있고 장차 제품을 빛내줄 기능이고 (예를 들면, 넷스케이프 2.0 테이블 기능), 다른 하나는 코딩하기도 아주 쉽고 프로그래머가 만들어보고 싶어하지만 쓸모는 별로 없는 기능이다 (예를 들면, BLINK 태그).

스케줄 없이 프로젝트를 진행할 경우, 프로그래머들은 쉽고 재미있는 기능 먼저 코딩하기 시작할 것이다. 그러다가 나중에 시간이 부족하게 되면, 없이 유용하고 중요한 기능을 포기하게 된다.

프로젝트를 개시하기 전에 미리 스케줄을 수립하는 경우, 무언가를 포기해야만 하는 상황이 되면, 쉽고 재미있는 기능을 포기하고 유용하고 중요한 기능을 작업하게 것이다. 스케줄을 관리하다 보면, 버려야 기능들을 프로그래머들이 스스로 추려내게 되기 때문에, 보다 나은 기능들로 구성된 강력하고 좋은 제품을 만들 있게 된다.

필자가 엑셀 5.0 개발 프로젝트에 참여했을 때의 일이다. 당시 우리 팀이 계획했던 기능들이 워낙 방대한 양이었기 때문에 스케줄을 맞출 수가 없게 되었다. 팀원들은 고민했다. 이를 어쩌나! 전부 너무나도 중요한 기능들인데. 매크로 편집 마법사는 절대로 포기할 없어!

시간이 촉박해지자 별다른 도리가 없었다. 제품 출시 기한을 맞추기 위해 우리 팀은 자식 같은 기능들을 잘라내야 했다. 포기하게 기능들에 대해 모두 마음 아파했다. 아픔을 달래기 위해 팀원들은 스스로에게 이야기했다. 기능들을 포기하는 것이 아니라 그저 조금 중요한 기능들을 위해 엑셀 6.0 버전으로 잠시미뤄두는 것뿐이라고.

엑셀 5 거의 완성되어갈 무렵, 필자는 동료인 Eric Michelman 함께 엑셀 6 버전에 대한 스펙을 만들기 시작했다. 사람은 머리를 맞대고 앉아 엑셀 5.0 스케줄을 조정하면서 엑셀 6”용으로 미뤄둔 기능들을 다시 살펴보았다. 우리는 기능들이라는 것이 너무나 조악한 것들 일색이라는 놀라지 않을 없었다. 쓸만한 기능이라고는 하나도 없었다. 물론, 기능들은 다음, 다음 버전에서도 제품화되지 않았다. 스케줄을 맞추기 위해 필요한 기능들만 추려낸 것이야말로 우리 팀이 중에서 가장 잘한 일이었다. 그렇게 하지 않았다면, 엑셀 5.0 개발 기간은 배쯤 길어졌을 것이고 아무짝에도 쓸모없는 쓰레기 같은 기능들로 50% 채워졌을 것이다. (넷스케이프 5.0이나 모질라(Mozilla) 경우에도 분명히 이와 같은 상황이 벌어졌을 것이다. 스케줄도 없고, 확정된 기능 목록도 없고, 쓸만한 기능만 추려내는 아무도 원치 않고, 그러다 보니 제품을 출시할 없게 것이다. 그들은 결국 IRC 클라이언트처럼 시간을 들일만한 가치라고는 없는 기능들만 잔뜩 들어있는 제품을 내놓게 것이다.)

부록: 엑셀을이용할알아두어야것들

엑셀이 소프트웨어 스케줄 관리에 편리한 제품이라고 주장하는 이유 중의 하나는 대부분의 엑셀 프로그래머들이 오로지 엑셀 하나만 가지고 스케줄을 관리한다는 사실이다 (what-if 시나리오 같은 것을 분석하는 사람은 별로 없다... 프로그래머들한테 그런 기대하면 되지!)

목록공유: 파일/목록 공유 명령을 이용하면 팀원 모두가 한꺼번에 파일을 열어두고 편집할 있다. 전체가 지속적으로 스케줄을 업데이트해야 하기 때문에, 기능은 아주 유용하다.

자동필터: 자동 필터 기능은 가령, 특정 프로그래머가 자신에게 할당된 작업만 전부 골라서 보고 싶을 스케줄을 필터링하는 편리하다. ‘자동 정렬 기능과 함께 사용하면, 자신에게 할당될 모든 작업을 우선순위에 따라 순서대로 조회할 있다. 자체로 효과적인 작업 계획서 되는 것이다. 끝내주지?

피벗테이블: 피벗 테이블 기능은 요약정보나 크로스테이블을 보고 싶을 매우 편리하다. 가령, 각각의 우선순위에 대해, 개발자에게 필요한 잔여시간을 보여주는 도표를 만들 있다. 피벗 테이블은 결코 어렵지 않다. 반드시 기능을 사용하는 익숙해져야 한다. 엑셀을 백만 배쯤 편리하게 써먹을 있기 때문이다.

작업일기능: 엑셀의 분석 툴팩에 포함된 작업일(Workday) 기능을 활용하면 스케줄에 해당하는 실제 달력상의 날짜를 손쉽게 확인할 있다.



이 기사는 영어로 Painless Software Schedules 라는 이름의 기사가 원본입니다
Posted by SB패밀리