[C#] 텍스트 에디터(윈도우 메모장) 만들기 10 마무리

2024. 2. 19. 16:11[C#]/[C# 윈폼] 혼자해보는 윈도우 메모장 만들기

오늘은 마무리 작업!

1. 페이지 설정

2. 상태 표시줄(확대/축소 로직 변경)

3. 바꾸기

4. 도움말 보기

 

 

 


1. 페이지 설정


변경 전 코드

// 클래스 레벨에 필드 추가
private PageSettings userPageSettings = null;

//페이지 설정
private void PageSettingToolTip_Click(object sender, EventArgs e)
{
    PageSetupDialog pageSetupDialog = new PageSetupDialog();
    pageSetupDialog.Document = new PrintDocument();

    // 이전 사용자 설정을 기본값으로 설정
    if (userPageSettings != null)
    {
        pageSetupDialog.PageSettings = userPageSettings;
    }

    if (pageSetupDialog.ShowDialog() == DialogResult.OK)
    {
        // 페이지 설정 적용
        PrintDocument printDocument = new PrintDocument();
        printDocument.DefaultPageSettings = pageSetupDialog.PageSettings;

        userPageSettings = pageSetupDialog.PageSettings;
    }
}

초기 값
설정 값 변경
다시 다이어로그를 들어왔을 때

  • 페이지 설정을 저장해서 다시 다이어로그를 띄었을 때, 설정된 값을 보여주기 위해 userPageSettings 변수를 추가해줬다.
  • 하지만 이 과정에서 문제가 발생했다.

 

밀리미터 - 인치 변환 문제

  • 우선 여기서 발생했던 문제점은 기본 값이 10, 10, 10, 10으로 설정되있었고, 여기서 왼쪽 여백을 40으로 변경하고 다시 페이지 설정에 들어오면 40, 10, 10, 10이 아닌 비율로 감소한 15.45, 3.94, 3.94, 3.94로 표시됐다.
  • 이는 기본 값이 밀리미터로 설정되어 있어서 생긴 문제였다. 인치와 밀리미터 변환 간의 2.94? 비율로 변환되서 표시되는 바람에 이런 문제가 생겼다.

 

변경 후 코드

private PageSettings pageSetting = new PageSettings();
private PrinterSettings printerSetting = new PrinterSettings();
   
   
   //페이지 설정
   private void PageSettingToolTip_Click(object sender, EventArgs e)
   {
       PageSetupDialog pageSetupDialog = new PageSetupDialog();
       pageSetupDialog.PageSettings = pageSetting;
       pageSetupDialog.PrinterSettings = printerSetting;
       pageSetupDialog.AllowPrinter = true;
       pageSetupDialog.AllowOrientation = true;
       pageSetupDialog.EnableMetric = true; //인치 - 밀리미터 문제

       // 페이지 설정 다이얼로그가 닫힐 때 이벤트 처리
       if (pageSetupDialog.ShowDialog() == DialogResult.OK)
       {
           pageSetting = pageSetupDialog.PageSettings;
       }

   }
  • pageSetupDialog.EnableMetric은 페이지 설정 대화 상자에서 길이 단위를 미터법(미리미터 및 센티미터)으로 사용할지 여부를 결정하는 Boolean 속성이다.
  • true로 설정해 길이를 인치가 아닌 밀리미터나 센티미터 단위로 입력하도록 했다.

기본 값

 

변경 중
변경 후 다시 다이어로그 들어왔을 때

 

 

 

2. 상태 표시줄

  • 라인과 글자 위치를 표시하고, 확대나 축소를 했을 때 줌 비율을 표시해주는 상태 표시줄이다.

  • 우선 StatusStrip 컨트롤을 추가해준다.
  • 처음에는 체크가 안되어있기 때문에 Visible을 False로 바꿔줬다.

 


메뉴 사이의 구분선 추가

  • 라인 구분선은 찾아봤더니 BorderStyle을 Etched로 변경하고, 라인이 생성될 방향(Right, Left 등)을 설정해주면 된다.

구분선 없을 때
구분선 표시


 

 

상태표시줄 활성화/비활성화

비활성화

 

활성화

    //상태 표시줄
    private void StatusBarToolStripMenuItem_Click(object sender, EventArgs e)
    {
        if (StatusBarToolStripMenuItem.Checked == false)
        {
            StatusBarToolStripMenuItem.Checked = true;
            statusStrip1.Visible = true;
        }
        else
        {
            StatusBarToolStripMenuItem.Checked = false;
            statusStrip1.Visible = false;
        }
    }
  • 우선 체크 여부에 따라 상태 표시줄을 표시하고 체크 상태를 표시하기 위해 클릭 이벤트를 추가해준다.

 

1) 현재 커서 위치 표시

      //커서 행과 열 정보
      private void UpdateStatusBar()
      {
          // 현재 커서의 행과 열 정보 가져오기
          int line = MyTextArea.GetLineFromCharIndex(MyTextArea.SelectionStart) + 1;
          int column = MyTextArea.SelectionStart - MyTextArea.GetFirstCharIndexOfCurrentLine() + 1;

          // 행과 열 정보 표시
          toolStripCursorPosition.Text = $"Ln {line}, Col {column}";

      }
  • UpdateStatusBar() 함수를 만들어 현재의 위치를 가져와 표시해준다.
//텍스트 선택 시 메뉴 활성화
private void MyTextArea_SelectionChanged(object sender, EventArgs e)
{
    bool isTextSelected = MyTextArea.SelectionLength > 0;
    CutTextToolTip.Enabled = isTextSelected;
    CopyTextToolTip.Enabled = isTextSelected;
    DeleteTextToolTip.Enabled = isTextSelected;
    UpdateStatusBar(); //추가
}
  • 위치가 바뀔 때 업데이트 하도록 추가해준다.

 

2) 줌 비율 표시


 

     //확대 메소드
     private void ZoomIn()
     {
         if (MyTextArea.ZoomFactor < 64)
         {
             MyTextArea.ZoomFactor += 0.1f;
             UpdateStatusBar();
         }
     }

     //축소 메소드
     private void ZoomOut()
     {
         if (MyTextArea.ZoomFactor > 0.2f)
         {
             MyTextArea.ZoomFactor -= 0.1f;
             UpdateStatusBar();
         }
     }

 

  • 계산 문제가 생각보다 복잡했다.
  • 로그를 찍어본 이유가 있었는데, 기존 코드에서 확대/축소를 했을 때 0.1씩 증감되는게 아닌 부동 소수점 문제가 있었다.
  • Float형을 다른 자료형으로 바꿔보기도 하고 여러가지 방법을 시도해봤다.

1차 변경된 코드

  //확대 메소드
  private void ZoomIn()
  {
      int zoomLevel = (int)(MyTextArea.ZoomFactor * 10);
      if (zoomLevel < 640) // 최대 확대 수준
      {
          zoomLevel += 1;
          MyTextArea.ZoomFactor = zoomLevel / 10f; // 정수로 변환된 값을 다시 소수점으로 변경
          Console.WriteLine($"ZoomFactors: {MyTextArea.ZoomFactor}");
          UpdateStatusBar();
      }
  }

  // 축소 메소드
  private void ZoomOut()
  {
      int zoomLevel = (int)(MyTextArea.ZoomFactor * 10); 
      if (zoomLevel > 20) // 최소 축소 수준(0.2를 기준으로 10을 곱한 값)
      {
          zoomLevel -= 1;
          MyTextArea.ZoomFactor = zoomLevel / 10f;
          Console.WriteLine($"ZoomFactors: {MyTextArea.ZoomFactor}");
          UpdateStatusBar();
      }
  }
  • 이 코드에서는 아주 여러가지 문제가 있었다. 우선 메뉴에서 확대/축소를 했을 때는 문제가 많이 없었다. 
  • 하지만 Ctrl+ Mouse Scroll을 통해 확대/축소할 경우 문제가 있었다.
  • ZoomFactor가 0.1씩 증감이 아닌 0.2씩 증감되다가 5.0이 넘어가면 0.1씩 제대로 증감되고 감소때는 2.0부터 감소가 되지 않았다.

 

  • 이는 MouseWheel 이벤트 문제였다. e.Delta 값으로 스크롤 방향을 알 수 있는데, 여기서 e.Delta에 스크롤 한번 당 -120, 120으로 계산되는 것이다. 

 

코드 수정본

 private int zoomLevel = 10; //추가
 
  // 확대하기
 private void ZoomInToolStripMenuItem_Click(object sender, EventArgs e)
 {
     ZoomIn();
 }

 // 축소하기
 private void ZoomOutToolStripMenuItem_Click(object sender, EventArgs e)
 {
     ZoomOut();
 }

 // 기본값으로
 private void DefaultToolStripMenuItem_Click(object sender, EventArgs e)
 {
     MyTextArea.ZoomFactor = 1;
     zoomLevel = 10; //추가
     UpdateStatusBar();
 }
 
//MouseWheel 이벤트 
private void MyTextArea_MouseWheel(object sender, MouseEventArgs e)
{
  if (ModifierKeys.HasFlag(Keys.Control))
  {
      if (e.Delta > 0)
      {
          ZoomIn();
      }
      else
      {
          ZoomOut();
      }
  }
}
 
 //확대 메소드 변경
 private void ZoomIn()
 {
     if (zoomLevel < 60) // 최대 확대 수준은 60입니다.
     {
         zoomLevel += 1; // 한 번의 스크롤에 대해 1씩 증가합니다.
         MyTextArea.ZoomFactor = zoomLevel / 10f; // ZoomFactor 설정
         Console.WriteLine($"ZoomFactors: {MyTextArea.ZoomFactor}");
         UpdateStatusBar();
     }
 }

 //축소 메소드 변경
 private void ZoomOut()
 {
     if (zoomLevel > 1) // 최소 축소 수준은 1입니다.
     {
         zoomLevel -= 1; // 한 번의 스크롤에 대해 1씩 감소합니다.
         MyTextArea.ZoomFactor = zoomLevel / 10f; // ZoomFactor 설정
         Console.WriteLine($"ZoomFactors: {MyTextArea.ZoomFactor}");
         UpdateStatusBar();
     }
 }
  • zoomLevel에 따라 증감을 해주는 방식으로 변경했다.
  • 이 코드는 MouseWheel시에도 zoomLevel을 고정한 다음 계산하기 때문에 상태표시줄에 표시가 잘되었다.

 

3. 바꾸기

  • 우선 폼을 하나 만들어 디자인을 해줍니다.

Memo Form(메인 폼)

 //바꾸기 폼 변수
 private ChangeForm changeDialog;
 public string lastChangeText = string.Empty; //마지막으로 바꿨던 값을 저장합니다.(다음에 열어도 기억할 수 있게)
 
  //바꾸기(Ctrl+H)
 private void ChangeTextToolTip_Click(object sender, EventArgs e)
 {
     ShowDialogs("change");
 }
 
 // 찾기, 줄 이동 다이얼로그 호출
private void ShowDialogs(string spec)
{
    if (spec == "find")
    {
        if (findDialog == null || findDialog.IsDisposed)
        {
            findDialog = new FindForm(this);
            findDialog.Show(this);
        }
        else
        {
            findDialog.BringToFront();
        }
    }
    else if(spec == "change")
    {
        if (changeDialog == null || changeDialog.IsDisposed)
        {
            changeDialog = new ChangeForm(this);
            changeDialog.Show(this);
        }
        else
        {
            changeDialog.BringToFront();
        }

    }
    }

 

ChangeForm.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 MyTextEditor
{
    public partial class ChangeForm : Form
    {
        메모장 Memo;

        public ChangeForm(메모장 mainMemo)
        {
            Memo = mainMemo; //메인 폼에 있는 값들을 사용하기 위해 메인 폼을 가져옵니다.
            InitializeComponent();
            textBoxToSearch.Text = Memo.lastSearchText; // 마지막으로 찾았던 문자를 찾을 내용 텍스트 박스에 초기화해줍니다.
            textBoxToChange.Text = Memo.lastChangeText; //마지막으로 바꿨던 문자를 바꿀 내용 텍스트 박스에 초기화해줍니다.
            caseCheckBox.Checked = Memo.IsCase; // 대/소문자 구분 상태를 초기화해줍니다.
        }


		//찾기 버튼
        private void FindButton_Click(object sender, EventArgs e)
        {
            string searchText = textBoxToSearch.Text;
            bool isCase = caseCheckBox.Checked;

            // 대/소문자 구분 설정 적용
            RichTextBoxFinds options = isCase ? RichTextBoxFinds.MatchCase : RichTextBoxFinds.None;

            if (!string.IsNullOrWhiteSpace(searchText))
            {
                Find(searchText, options);
            }
            else
            {
                MessageBox.Show("찾을 내용을 입력하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

		//찾기 메소드
        private void Find(string searchText, RichTextBoxFinds options)
        {
            Memo.lastSearchText = searchText; // 마지막으로 찾은 문자열 저장
            
            int startIndex = Memo.MyTextArea.SelectionStart + Memo.MyTextArea.SelectionLength;
            int resultIndex = Memo.MyTextArea.Find(searchText, startIndex, options);

            if (resultIndex != -1)
            {
                Memo.MyTextArea.Select(resultIndex, searchText.Length);
            }
            else
            {
                MessageBox.Show($"'{searchText}'를 찾을 수 없습니다.", "찾기", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            Memo.MyTextArea.Focus(); //Focus를 해줘야 메모장 폼에 Focus가 되서 찾은 다음 찾은 내용이 보입니다.
        }


		//바꾸기 버튼
        private void ChangeButton_Click(object sender, EventArgs e)
        {
            // 선택된 텍스트 바꾸기
            string searchText = textBoxToSearch.Text;
            string replaceText = textBoxToChange.Text;

            RichTextBoxFinds options = caseCheckBox.Checked ? RichTextBoxFinds.MatchCase : RichTextBoxFinds.None;
    		Find(searchText,options); //이 부분은 찾은 내용이 선택되야 바꿀 수 있기 때문에 초기에 한 번 찾아줍니다. 

            Memo.lastChangeText = textBoxToChange.Text;

            if (!string.IsNullOrWhiteSpace(searchText))
            {
                Memo.MyTextArea.SelectedText = replaceText;
                Memo.lastChangeText = replaceText;
            }
            else
            {
                MessageBox.Show("찾을 내용을 입력하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            Memo.MyTextArea.Focus();
        }

		//전체 바꾸기
        private void AllChangeButton_Click(object sender, EventArgs e)
        {
            // 모든 텍스트 바꾸기
            string searchText = textBoxToSearch.Text;
            string replaceText = textBoxToChange.Text;
            bool isCase = caseCheckBox.Checked;
            Memo.lastChangeText = textBoxToChange.Text;

            // 대/소문자 구분 설정 적용
            RichTextBoxFinds options = isCase ? RichTextBoxFinds.MatchCase : RichTextBoxFinds.None;

            int currentIndex = 0;
            while (currentIndex < Memo.MyTextArea.TextLength)
            {
                int resultIndex = Memo.MyTextArea.Find(searchText, currentIndex, options);

                if (resultIndex != -1)
                {
                    Memo.MyTextArea.SelectedText = replaceText;
                    currentIndex = resultIndex + replaceText.Length;
                }
                else
                {
                    break;
                }
            }
            Memo.lastChangeText = replaceText;
            Memo.MyTextArea.Focus();
        }

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

        // 대/소문자 구분 체크박스 이벤트 핸들러
        private void CaseCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            Memo.IsCase = caseCheckBox.Checked;
        }
    }
}

바꾸기
모두 바꾸기

 

4. 도움말 보기

Process.Start("[url]");
  • Process.Start(url)을 이용해 내 블로그를 열어주려 했다.
  • 하지만 아래와 같이 열 수 없다는 에러 메시지가 발생했다.
예외 발생 : 'System.ComponentModel.Win32Exception'(System.Diagnostics.Process.dll)'System.ComponentModel.Win32Exception' 형식의 예외가 System.Diagnostics.Process.dll에서 발생했지만 사용자 코드에서 처리되지 않았습니다. An error occurred trying to start process 'url 주소' with working directory 'C:\Users\source....\bin\Debug\net8.0-windows'. 지정된 파일을 찾을 수 없습니다.

 

 

변경된 코드

//도움말
private void QAToolStripMenuItem_Click(object sender, EventArgs e)
{
    Process.Start(new ProcessStartInfo
    {
        FileName = "cmd",
        Arguments = $"/c start [url]",
        WindowStyle = ProcessWindowStyle.Hidden
    });

}
  • 원래 코드에서는 Process.Start 메서드를 사용하여 URL을 직접 실행하려고 했지만, 이 방식은 URL을 실행할 수 없는 파일로 인식하여 오류가 발생했다.
  • 웹 브라우저를 실행하고 해당 URL을 웹 브라우저에서 열도록 수정했다.
  • [url] 부분에 열고싶은 웹 사이트 주소를 넣어주면 잘 작동한다.

메모장 만들기 간단하지만 생각보다 에러 잡는 것이 까다로울 때도 있었지만 마무리 했습니다.

코드는 깃허브에 올려놓을 예정입니다. 

반응형