반응형

시간과 임의의 값을 비동기적으로 텍스트 파일로 저장하고 확인해 보자. 

 

 

namespace DateTimeValue
{
    public partial class Form1 : Form
    {
        private readonly string filePath = "output.txt";        
        readonly Random random = new Random();

        public Form1()
        {
            InitializeComponent();

            button1.Click += Button1_Click;
            button2.Click += Button2_Click;
        }

        // 비동기적으로 파일에 현재 시간과 랜덤 값을 기록하는 버튼 클릭 이벤트 핸들러
        // 비동기 메서드는 Task를 반환하는게 좋지만 이벤트 핸들러는 void를 반환해야 하므로 어쩔 수 없이 async void를 사용
        private async void Button1_Click(object? sender, EventArgs e)
        {
            try
            {
                // 버튼 클릭 시 버튼을 비활성화하여 중복 클릭 방지
                button1.Enabled = false; // 버튼 비활성화

                FileStream fileStream = new FileStream (
                    filePath,
                    FileMode.Append,  // 파일이 없으면 새로 생성하고, 있으면 이어쓰기
                    FileAccess.Write, // 쓰기 전용
                    FileShare.Read); // 다른 프로세스가 파일을 읽을 수 있도록 허용, 내가 쓰는 동안 다른 프로세스는 읽기 가능
                
                using (StreamWriter writer = new StreamWriter(fileStream, System.Text.Encoding.UTF8))
                {
                    for (int i = 0; i < 5; i++)
                    {
                        await writer.WriteLineAsync(DateTime.Now.ToString("yyyyMMdd-HHmmss") + " " + random.Next());
                        // 비동기적으로 파일에 기록
                        await writer.FlushAsync();
                        // 비동기적으로 버퍼를 파일에 기록

                        // 비동기적으로 1초 대기
                        await Task.Delay(1000);
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"오류 발생: {ex.Message}");
            }
            finally
            {
                button1.Enabled = true; // 버튼 활성화
            }
        }

        private async void Button2_Click(object? sender, EventArgs e)
        {
            listBox1.Items.Clear(); // 기존 항목 제거
            try
            {
                FileStream fileStream = new FileStream(
                    filePath,
                    FileMode.Open, // 파일이 없으면 예외 발생
                    FileAccess.Read, // 읽기 전용
                    FileShare.ReadWrite); // 다른 프로세스가 파일을 읽을 수 있도록 허용, 내가 읽는 동안 다른 프로세스도 읽기/쓰기 가능

                using (StreamReader reader = new StreamReader(fileStream, System.Text.Encoding.UTF8))
                {
                    string? line;
                    while ((line = await reader.ReadLineAsync()) != null)
                    {
                        //listBox1.Items.Add(line);

                        // 공백 문자를 기준으로 분리
                        string[] parts = line.Split(' ');
                        if (parts.Length == 2)
                        {
                            listBox1.Items.Add("시간: " + DateTime.ParseExact(parts[0], "yyyyMMdd-HHmmss", null) + "\t값: " + parts[1]);
                            // ParseExact() 세번째 인자를 null로 주면 현재 문화권의 형식으로 해석, InvariantCulture를 주면 문화권에 상관없이 해석
                        }
                        else
                        {
                            listBox1.Items.Add("Error");
                        }
                    }
                }
            }
            catch (FileNotFoundException)
            {
                MessageBox.Show($"{filePath} 파일이 존재하지 않습니다.");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"오류 발생: {ex.Message}");
            }            
        }
    }
}

 

Record 버튼을 클릭해 로그 파일을 생성하고 Show 버튼을 클릭해 원하는 로그 파일을 선택하면 리스트 박스에 로그가 표시된다.

 

그래프를 추가해 보자.

 

Graph 버튼을 추가한다.

 

using System.Diagnostics;

namespace DateTimeValue
{
    public partial class Form1 : Form
    {
        internal string filePath = string.Empty; // Form2에서 접근할 수 있도록 internal로 선언
        readonly Random random = new Random();

        public Form1()
        {
            InitializeComponent();

            button1.Click += Button1_Click;
            button2.Click += Button2_Click;
            button3.Click += Button3_Click;
        }

        // 비동기적으로 파일에 현재 시간과 랜덤 값을 기록하는 버튼 클릭 이벤트 핸들러
        // 비동기 메서드는 Task를 반환하는게 좋지만 이벤트 핸들러는 void를 반환해야 하므로 어쩔 수 없이 async void를 사용
        private async void Button1_Click(object? sender, EventArgs e)
        {
            try
            {
                // 버튼 클릭 시 버튼을 비활성화하여 중복 클릭 방지
                button1.Enabled = false; // 버튼 비활성화

                int count = 120; // 테스트를 위해 120회(120초)만 기록
                string mm = string.Empty;
                string now = string.Empty;
                bool needNewFile = false; // 새로운 파일 생성 여부를 나타내는 플래그

                while (true)
                {
                    filePath = DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".txt"; // 파일 이름을 현재 시간으로 설정                    
                    mm = Path.GetFileName(filePath).Substring(11, 2); // 파일 이름에서 '분'만 추출

                    FileStream fileStream = new FileStream(
                        filePath,
                        FileMode.Append,  // 파일이 없으면 새로 생성하고, 있으면 이어쓰기
                        FileAccess.Write, // 쓰기 전용
                        FileShare.Read); // 다른 프로세스가 파일을 읽을 수 있도록 허용, 내가 쓰는 동안 다른 프로세스는 읽기 가능

                    using (StreamWriter writer = new StreamWriter(fileStream, System.Text.Encoding.UTF8))
                    {
                        while (true)
                        {
                            now = DateTime.Now.ToString("yyyyMMdd-HHmmss");
                            if (now.Substring(11, 2) != mm) // 현재 '분'이 파일 이름의 '분'과 다르면
                            {
                                needNewFile = true;
                                break; // 루프 종료하여 새로운 파일 생성
                            }

                            await writer.WriteLineAsync(now + " " + random.Next(1, 11)); // 1부터 10까지의 랜덤 값을 기록
                            // 비동기적으로 파일에 기록
                            await writer.FlushAsync();
                            // 비동기적으로 버퍼를 파일에 기록

                            await Task.Delay(1000);
                            // 비동기적으로 1초 대기

                            count--;
                            if (count < 0)
                                break; // 기록 횟수가 0이 되면 루프 종료
                            Debug.WriteLine($"남은 기록 횟수: {count}");
                        }
                    }

                    // needNewFile보다 count를 먼저 확인하여 무한 루프를 방지
                    if (count < 0)
                    {
                        break; // 무한 루프 종료
                    }
                    if (needNewFile)
                    {
                        needNewFile = false; // 새로운 파일 생성 여부 플래그 초기화
                        continue; // 새로운 파일 생성 루프 시작
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"오류 발생: {ex.Message}");
            }
            finally
            {
                button1.Enabled = true; // 버튼 활성화
            }
        }

        private async void Button2_Click(object? sender, EventArgs e)
        {
            FileDialog fileDialog = new OpenFileDialog();
            fileDialog.InitialDirectory = Application.StartupPath; // 실행 파일이 있는 폴더 경로
            fileDialog.Filter = "텍스트 파일 (*.txt)|*.txt|모든 파일 (*.*)|*.*";
            fileDialog.Title = "파일 선택";
            fileDialog.DefaultExt = "txt";

            if (fileDialog.ShowDialog() == DialogResult.OK)
                filePath = fileDialog.FileName;
            else
                return; // 파일 선택 취소 시 종료

            listBox1.Items.Clear(); // 기존 항목 제거

            try
            {
                FileStream fileStream = new FileStream(
                    filePath,
                    FileMode.Open, // 파일이 없으면 예외 발생
                    FileAccess.Read, // 읽기 전용
                    FileShare.ReadWrite); // 다른 프로세스가 파일을 읽을 수 있도록 허용, 내가 읽는 동안 다른 프로세스도 읽기/쓰기 가능

                using (StreamReader reader = new StreamReader(fileStream, System.Text.Encoding.UTF8))
                {
                    string? line;
                    while ((line = await reader.ReadLineAsync()) != null)
                    {
                        //listBox1.Items.Add(line);

                        // 공백 문자를 기준으로 문자열 분리
                        string[] parts = line.Split(' ');
                        if (parts.Length == 2)
                        {
                            listBox1.Items.Add("시간: " + DateTime.ParseExact(parts[0], "yyyyMMdd-HHmmss", null) + "\t값: " + parts[1]);
                            // ParseExact() 세번째 인자를 null로 주면 현재 문화권의 형식으로 해석, InvariantCulture를 주면 문화권에 상관없이 해석
                        }
                        else
                        {
                            listBox1.Items.Add("Error");
                        }
                    }
                }
            }
            catch (FileNotFoundException)
            {
                MessageBox.Show($"{filePath} 파일이 존재하지 않습니다.");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"오류 발생: {ex.Message}");
            }
        }

        private void Button3_Click(object? sender, EventArgs e)
        {
            Form2 dlg = new Form2();
            dlg.Owner = this; // Form1을 Form2의 소유자로 설정
            // 아니면 아래 if문에서 dlg.ShowDialog(this) 명령으로 Form1을 Form2의 소유자로 설정할 수도 있다
            if (dlg.ShowDialog() == DialogResult.OK)
                ; // Form2에서 OK 버튼을 클릭하면 처리할 내용 작성

            dlg.Close();
            dlg.Dispose();
        }
    }
}

 

Visual Studio - Project - Add Form(Windows Forms)... 을 클릭하고 Form을 하나 추가한다.

 

버튼을 하나 배치한다.

 

NuGet Package Manager - LiveChartsCore.SkiaSharpView.WinForms 를 설치한다.

 

using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.WinForms;

using System.Collections.ObjectModel;
using System.Diagnostics;

namespace DateTimeValue
{
    public partial class Form2 : Form
    {
        private readonly ObservableCollection<DateTimePoint> values = new ObservableCollection<DateTimePoint>();

        public Form2()
        {
            InitializeComponent();

            button1.DialogResult = DialogResult.OK; // 버튼 클릭 시 DialogResult를 OK로 설정
            this.Load += Form2_Load; // Form2 로드 이벤트에 핸들러 추가                        
        }

        private async void Form2_Load(object? sender, EventArgs e)
        {
            Form1? parent = Owner as Form1; // Form1 인스턴스를 가져옴

            if (parent != null)
                if (string.IsNullOrEmpty(parent.filePath))
                {
                    // 파일 경로가 설정되지 않은 경우, 메시지 박스를 표시하고 종료
                    MessageBox.Show("파일 경로가 설정되지 않았습니다.");
                    // Form2 종료
                    Close();
                }
                else
                {
                    try
                    {
                        FileStream fileStream = new FileStream(
                            parent.filePath,
                            FileMode.Open, // 파일이 없으면 예외 발생
                            FileAccess.Read, // 읽기 전용
                            FileShare.ReadWrite); // 다른 프로세스가 파일을 읽을 수 있도록 허용, 내가 읽는 동안 다른 프로세스도 읽기/쓰기 가능

                        using (StreamReader reader = new StreamReader(fileStream, System.Text.Encoding.UTF8))
                        {
                            string? line;
                            while ((line = await reader.ReadLineAsync()) != null)
                            {
                                //listBox1.Items.Add(line);

                                // 공백 문자를 기준으로 분리
                                string[] parts = line.Split(' ');
                                if (parts.Length == 2)
                                {
                                    values.Add(new DateTimePoint(DateTime.ParseExact(parts[0], "yyyyMMdd-HHmmss", null), double.Parse(parts[1])));
                                    // ParseExact() 세번째 인자를 null로 주면 현재 문화권의 형식으로 해석, InvariantCulture를 주면 문화권에 상관없이 해석
                                }
                                else
                                {
                                    Debug.WriteLine($"잘못된 형식의 데이터: {line}");
                                }
                            }

                            ISeries[] series = new ISeries[]
                            {
                                new LineSeries<DateTimePoint>
                                {
                                    Values = values,
                                    Fill = null
                                }
                            };

                            CartesianChart cartesianChart = new CartesianChart
                            {
                                Series = series,
                                ZoomMode = LiveChartsCore.Measure.ZoomAndPanMode.X,
                                Location = new System.Drawing.Point(0, 0),
                                Size = new System.Drawing.Size(800, 300),
                                Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom,
                                // X축 설정
                                XAxes = new Axis[]
                                {
                                    //new Axis
                                    //{
                                    //    Labeler = value => new DateTime((long)value).ToString("HH:mm:ss"),
                                    //    MinStep = TimeSpan.FromSeconds(1).Ticks // 1초 단위 (Ticks 단위)
                                    //    // MinStep을 설정하면 축의 최소 간격을 지정할 수 있는데 DateTimePoint 데이터를 사용하는
                                    //    // 경우에는 제대로 동작하지 않는다. 이 경우에는 DateTimeAxis를 사용하는 것이 더 적합하다.
                                    //}

                                    // 1초 단위 축 생성 및 DateTime 포맷터 지정
                                    // 위 주석 처리된 Axis를 직접 생성하는 것보다 DateTimeAxis를 사용하는 것이 더 간단하고 직관적이다
                                    new DateTimeAxis(TimeSpan.FromSeconds(1), date => date.ToString("HH:mm:ss"))
                                    //{
                                    //    Name = "Time",
                                    //    MinStep = TimeSpan.FromSeconds(2).Ticks, // 최소 2초 간격 유지를 원하는 경우
                                    //    LabelsRotation = 15 // 라벨 회전 각도
                                    //}
                                },
                                // Y축 설정
                                //YAxes = new Axis[]
                                //{
                                //    new Axis
                                //    {
                                //        Labeler = value => value.ToString("F2") // 소수점 2자리까지 표시
                                //    }
                                //}
                            };

                            Controls.Add(cartesianChart);
                        }
                    }
                    catch (FileNotFoundException)
                    {
                        MessageBox.Show($"{parent.filePath} 파일이 존재하지 않습니다.");
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show($"오류 발생: {ex.Message}");
                    }
                }
        }
    }
}

 

프로젝트를 빌드하고 실행한다.

 

Record 버튼을 클릭해 로그 파일을 생성하고 Show 버튼을 클릭해 원하는 로그 파일을 선택하면 리스트 박스에 로그가 표시된다.

 

Form1에서 Graph 버튼을 클릭하면 Form2가 열리고 그래프가 표시된다.

 

마우스로 Zooming 및 Panning을 할 수 있다.

※ 참고

2026.04.20 - [C#] - [LiveCharts] 설치 및 기본 차트 그리기

 

반응형
Posted by J-sean
: