[dot Net] 윈도우 시스템 종료 프로그램 만들기 <下>

.net으로 일정 시간이 되면 윈도우 시스템을 종료하는 프로그램 만들기 하편입니다.

윈도우 시스템 종료 프로그램 만들기 上편에 이어서 작성되었습니다.


목표

1) 윈도우 시스템을 종료하는 프로그램을 만든다.

2) 간단한 UI를 가진다.

3) 프로그램은 종료되지 않고 트레이 아이콘으로 유지한다.

4) 매일 특정 시간에 종료가 가능하게 설정한다.

5) 종료시간이 임박하면 사용자에게 즉시 종료와 취소를 가능하게 한다.


이번에는…

매일 특정 시간에 종료하게 하기 위해 간단하게 Registry를 사용하는 부분과

종료시간이 임박하면 [즉시 종료]와 [종료 취소]를 선택하는 다이얼로그 창을 구현할 예정입니다.




윈도우 레지스트리 사용


윈도 레지스트리는 윈도우 프로그램 개발자에게는 정말 친숙합니다.

OS를 비롯한 H/W, S/W의 여러 정보와 설정이 저장되어 있는 데이터베이스인데요.

프로그램을 만들 때도 환경설정 등의 정보를 저장하기 위해서 많이 사용합니다.

Windows 3.1 까지만 해도 환경설정 파일인 INI파일을 이용했었다는데

Windows 95 그러니까 24년 전부터 지금까지 이용되고 있습니다.


레지스트리는 친절한 프로그램으로 가기 위해 주로 사용합니다.


사용자가 원하는 설정을 제공해 주는 용도를 기본으로

사용자가 특정 환경에서 종료했을 때 환경을 이어서 사용할 수 있게 도와줍니다.

예를 들어 매번 켜서 전체화면으로 변경하는 것이 아니라

상태를 저장해서 전체화면으로 종료하면 전체화면으로 시작될 수 있게

레지스트리에 상태 값을 저장하고 실행할 때 반영되게 만듭니다.

물론 반드시 윈도 레지스트리를 사용해야 하는 것은 아니지만

레지스트리가 주로 설정을 저장하는 형태로 많이 쓰이며

설정을 고민하고 많이 저장하는 것이 보다 사용자를 생각하고 배려하는 것과 밀접하다 생각합니다.


이 프로그램에서는 단순하게 종료 여부와 종료 시간을 저장하기 위해서 사용하였습니다.

레지스트리 사용하는 방법이야 Win API 부터 시작해서 정말 다양한 방법이 있겠지만

.net의 간단한 방법으로 클래스를 만들어 사용하고 있습니다.


먼저 키 경로 설정을 합니다.

public static RegistryKey regKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\MusmaWindowClose", true);


Form Load 시에는 레지스트리 값이 반영되는 부분을 구현했습니다.

private void Form1_Load(object sender, EventArgs e)
{
    if (regKey != null)
    {
        if (Convert.ToString(regKey.GetValue("SetWindowClose","false")) == "true")
        {
            CheckBox1.CheckState = CheckState.Checked;
            Label3.Enabled = true;
            Label4.Enabled = true;
        }

        //시, 분 from 레지스트리//
        int Hour = Convert.ToInt32(regKey.GetValue("Hour","23"));
        int Minute = Convert.ToInt32(regKey.GetValue("Minute","0"));
        //시, 분 from 레지스트리//

        dateTime = new DateTime(2017, 11, 11, Hour, Minute, 0);
        DateTimePicker1.Value = dateTime;
    }
}


일반적으로 확인 버튼으로 상태를 레지스트리에 저장하겠지만

간단한 프로그램이니 동기화 시켰습니다.

//checkbox의 체크상태 변경 시
private void CheckBox1_CheckedChanged(object sender, EventArgs e)
{
    if(CheckBox1.CheckState == CheckState.Unchecked) //언체크
    {
        Label3.Enabled = false;
        Label3.Text = "00:00:00";
        //자식에 라벨 전달
        formWarning.SetLabelTime(Label3.Text);
        Label4.Enabled = false;

        //레지스트리 체크상태 false 저장
        regKey.SetValue("SetWindowClose", "false");
    }
    else //체크
    {
        Label3.Enabled = true;
        Label4.Enabled = true;

        //레지스트리 체크상태 true 저장
        regKey.SetValue("SetWindowClose", "true");
    }
}


//dateTimePicker 값 변경 시
private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
{
    //Form이 Load 될 때 적용되는 1회 변경 제외
    if (!IsFirstDateTimePicker)
    {
        //dateTimePicker 읽기
        int Hour = DateTimePicker1.Value.Hour;
        int Minute = DateTimePicker1.Value.Minute;

        if (regKey != null)
        {
            //시, 분 to 레지스트리//
            regKey.SetValue("Hour", Hour.ToString());
            regKey.SetValue("Minute", Minute.ToString());
            //시, 분 to 레지스트리//
        }
    }
    else
    {
        IsFirstDateTimePciker = false;
    }
}


값을 모두 string으로 저장하니까 죄짓는 기분이 드네요.

레지 읽고 쓰기를 완료하였습니다.




팝업 창 만들기


종료시간이 임박하면 사용자에게 종료한다고 알리고

즉시 종료나 종료 취소가 가능하게 하기 위해서 팝업 창을 만들겠습니다.

팝업창

실행 화면입니다. UI는 버튼 2개와 그룹박스, 라벨로만 만들었습니다.

개인적으로 팝업 창은 직관적인 OK, CANCEL 형태를 선호합니다만

즉시 종료 버튼은 OK로 볼 수는 없겠습니다.


메인 설정 창은 트레이 아이콘으로 숨기지만 해당 팝업창은

시스템을 종료 시킬 거니까 항시 상단에 유지해야하고 보여줘야 합니다.

해당 폼의 속성 TopMost를 True 둡니다.


//부모
private void Form1_Load(object sender, EventArgs e)
{
    //경고창 생성
    formWarning = new FormWarning(this);
    formWarning.Visible = false;
}

//자식
Form1 ParentForm;
public FormWarning(Form1 form)
{
    this.ParentForm = form;
    InitializeComponent();
}

이런 식으로 자식 다이얼로그에 부모 DNA를 심었습니다.

자식은 항상 있는데 30초 남았을 때만 불러서 보여주려 합니다.


//부모에서 라벨 시간 지정
public void SetLabelTime(string timetext)
{
    Label3.Text = timetext;
}

//종료 시 취소 질의
private void FormWarning_FormClosing(object sender, FormClosingEventArgs e)
{
    //부모가 종료 될 때는 예외 바로 종료
    if (!ParentForm.IsMainExit)
    {
        if (MessageBox.Show("취소하시겠습니까?", "Warning", MessageBoxButtons.OKCancel) == DialogResult.OK)
        {
            ParentForm.IsUserDefineRestartCancel = true;
            this.Visible = false;
        }

        //종료는 취소
        e.Cancel = true;
    }
}

//재시작 실행 버튼
private void ButtonOK_Click(object sender, EventArgs e)
{
    if (MessageBox.Show("즉시 종료 하시겠습니까?", "Warning", MessageBoxButtons.OKCancel) == DialogResult.OK)
    {
        this.Visible = false;
        System.Diagnostics.Process.Start("shutdown", "/s /f /t 0");
    }
}

//재시작 취소 버튼
private void ButtonCancel_Click(object sender, EventArgs e)
{
    if (MessageBox.Show("취소하시겠습니까?", "Warning", MessageBoxButtons.OKCancel) == DialogResult.OK)
    {
        ParentForm.IsUserDefineCancel = true;
        this.Visible = false;
    }
}

팝업 창인 자식 부분입니다.

자식은 종료를 할 때 숨기기만 하게 FormClosing 함수는 사용해야 했습니다.




구현

다시 메인 설정 창으로 돌아가

이전에 등록해둔 timer에 팝업창과 함께 예외 내용을 적용합니다.

private void timer1_Tick(object sender, EventArgs e)
{
    //종료되기까지 남은 시간
    if (CheckBox1.CheckState == CheckState.Checked)
    {
        //DateTimePicker 읽기
        int Hour = DateTimePicker1.Value.Hour;
        int Minute = DateTimePicker1.Value.Minute;

        DateTime CloseDateTime = new DateTime(CurrentDateTime.Year, CurrentDateTime.Month, CurrentDateTime.Day, Hour, Minute, 0);
        
        // CurrentDateTime가 더 느린 경우
        if(DateTime.Compare(CurrentDateTime, CloseDateTime)>0) 
        {
            //재시작시간을 다음날로 계산
            CloseDateTime = RestartDateTime.AddDays(1);
        }

        //시간 차이 계산
        TimeSpan timeSpan = CloseDateTime - CurrentDateTime;
        //빼기 차이 1초 적용
        TimeSpan timeSpanPlus = new TimeSpan(0, 0, 1);
        timeSpan = timeSpan.Add(timeSpanPlus);
        //시간 차이 표시
        Label3.Text = timeSpan.Hours.ToString("D2") + ":" + timeSpan.Minutes.ToString("D2") + ":" + timeSpan.Seconds.ToString("D2");

        //자식에게 시간 전달
        formWarning.SetLabelTime(Label3.Text);

        //30초이하 종료가 임박할 때
        if (timeSpan.Hours==0 && timeSpan.Minutes ==0 && timeSpan.Seconds <= 30)
        {
            //사용자가 취소를 눌렸을 경우에는 재시작 하지 않음
            if (!IsUserDefineCancel)
            {
                Label3.ForeColor = Color.IndianRed;
                //경고창이 없으면 시작
                if (!formRestartWarning.Visible)
                {
                    formRestartWarning.Visible = true;
                }

                if (timeSpan.Seconds == 0)
                {
                    //프로그램 종료
                    System.Diagnostics.Process.Start("shutdown", "/s /f /t 0");
                }
            }
            else //사용자 취소에 의한 종료 24시간 증가
            {
                Label3.ForeColor = Color.Black;
                int UserHour = timeSpan.Hours + 24;
                Label3.Text = UserHour.ToString("D2") + ":" + timeSpan.Minutes.ToString("D2") + ":" + timeSpan.Seconds.ToString("D2");
                formWarning.SetLabelTime(Label3.Text);
            }
        }
        else
        {
            //임박 범위가 벗어날 때 초기화
            IsUserDefineCancel = false;
            Label3.ForeColor = Color.Black;
            //경고창이 있으면 종료
            if (formWarning.Visible)
            {
                formWarning.Visible = false;
            }
        }
    }
}

메인 설정 창 타이머에 Interval을 1000 밀리초 이하로 설정

해당 함수를 구현했습니다.

최대한 단순하게 만들려고 생각했는데 생각보다 처리된 예외가 많아 보입니다.




윈도우 시스템 종료 프로그램 완료했습니다.


감사합니다.