재현 가능한 최소한의 예제 프로젝트란? - 두 번째 예제
재현 가능한 최소한의 예제는, 자신이 만들고 있던 프로그램을 보내달라는 것이 아닙니다. 예를 들어, 아래의 글에서 설명한 것이 그런 사례입니다.
재현 가능한 최소한의 예제 프로젝트란?
; https://www.sysnet.pe.kr/2/0/11452
하나 더 예를 들어볼까요? 다음과 같은 질문이 있습니다.
안녕하십니까. c# Winform UI 질문드리겠습니다!
; https://www.sysnet.pe.kr/3/0/5309
이와 함께 보내온 예제 프로젝트는 아래와 같고,
파일들은 다음과 같은 식의 코드를 포함하고 있습니다.
// UIProgressBar.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Threading;
namespace Socket_UIProgressbar
{
public partial class UIProgressBar : UserControl
{
#region 필드
string SvrIP;
int SvPort;
string filepath; //선택된 경로를 담는 string 변수
//경로에 있는 파일의 총 개수와 실제로 보내지는 파일의 Count;
int TotalFileCnt = 0;
int sendfilecnt = 0;
//진행률을 표시하기위해 사용될 double형 변수
double totalpercent;
double filepercent;
//실제로 파일을 보내게 될 FileOut Class
FileOut fileout;
#endregion
public UIProgressBar(string ip, int port) //연결 할 서버의 ip와 port
{
InitializeComponent();
SvrIP = ip;
SvPort = port;
}
private void Select_FilePath_Click(object sender, MouseEventArgs e) //경로선택 버튼 MouseClick Event
{
var dialog = new FolderBrowserDialog();
if (dialog.ShowDialog() == DialogResult.OK)
filepath = dialog.SelectedPath;
textBox1.Text = filepath; //의미는 없습니다. 최대한 비슷한 환경 샘플을 위해 선택한 경로를 보여주는 텍스트박스 입니다.
}
private void File_Send_MouseClick(object sender, MouseEventArgs e)//파일 전송 버튼 MouseClick Event
{
if (filepath == "")
{
MessageBox.Show("폴더를 선택해주세요.");
return;
}
else
Set_SendFile();
}
private void Set_SendFile()
{
fileout = new FileOut(SvrIP, SvPort);
DirectoryInfo di = new DirectoryInfo(filepath);
FileSystemInfo[] infos = di.GetFileSystemInfos();
//선택 폴더의 파일 총갯수
TotalFileCnt = di.GetFiles("*", System.IO.SearchOption.AllDirectories).Length;
//프로그래스바 세팅
Set_PorgressBar(TotalFileCnt);
//서버가 받는 경로에 해당 폴더가 없으면 만들고, 파일의 FullName을 넘긴다. FileOut class에.
SendFiles(infos);
}
private void SendFiles(FileSystemInfo[] infos) //폴더경로이름과 파일이름을 보내는 재귀함수. 샘플이기에 불필요한 부분은 주석처리했습니다.
{
if (infos == null)
throw new ArgumentNullException("infos");
foreach (FileSystemInfo fi in infos)
{
if (fi is DirectoryInfo)
{
try
{
DirectoryInfo dinfo = (DirectoryInfo)fi;
Thread.Sleep(1500);
SendFiles(dinfo.GetFileSystemInfos());
}
catch (Exception ex)
{ Console.WriteLine(ex.ToString()); }
}
else if (fi is FileInfo)
{
try
{
FileInfo Finfo = (FileInfo)fi;
// string subfolder = null;
if (Finfo.DirectoryName.Length > filepath.Length)
{
//subfolder = Finfo.DirectoryName.Substring(filepath.Length + 1);
//asynchronousClient.StartClient(SIP, SPort, Packet.SetPath(subfolder + @"\"));
Thread.Sleep(1500);
}
else
{
//subfolder = @"\\";
//asynchronousClient.StartClient(SIP, SPort, Packet.SetPath(subfolder + @"\"));
Thread.Sleep(1500);
}
++sendfilecnt;
fileout.Run(fi.FullName);
//실제로 문제가 되는 부분입니다. 파일 전송과 동시에 프로그레스바를 업데이트하고 label1에 진행정도를 바꿔주는 작업인데.
//프로그레스바 반응이 항상 한박자 느려서 label1의 텍스트가 항상 앞서나갑니다.
progressBar1.Value = sendfilecnt;
progressBar1.Refresh();
label1.Text = totalpercent.ToString() + "%";
label1.Update();
totalpercent += filepercent;
Thread.Sleep(1500);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
private void Set_PorgressBar(int totalFileCnt)
{
int MaximumProgbar = totalFileCnt;
progressBar1.Value = 0;
progressBar1.Maximum = MaximumProgbar;
//Label에 현재 진행률을 표시하기 위한 초기화 작업.-
totalpercent = (double)100 / MaximumProgbar;
filepercent = (double)100 / MaximumProgbar;
}
}
}
// FileOut.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Socket_UIProgressbar
{
class FileOut
{
#region 필드
string Ip;
int Port;
Thread t; //파일을 전송을 담당할 백그라운드 스레드
#endregion
public FileOut(string ip, int port)
{
Ip = ip;
Port = port;
}
public void Run(string filepath)
{
t = new Thread(new ParameterizedThreadStart(sendthread)); // 스레드 생성
t.IsBackground = true;
t.Start(filepath);
}
private void sendthread(object obj)
{
try
{
string filepath = (string)obj; //보낼 파일의 경로
string[] p = filepath.Split('\\');
string filename = p[p.Count() - 1]; //보낼 파일의 이름
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //소켓의 타입을 정한다.
//socket.Connect(IPAddress.Parse(ip.Address.ToString()), 1010); //소켓 접속요청 포트번호 1010
socket.Connect(Ip, Port); //소켓 접속요청 포트번호 1010
FileStream fileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read); //filepath의 파일을 불러옴
// 파일 크기 전송
int fileLength = (int)fileStream.Length; //파일 크기를 구함
byte[] fileBuffer = BitConverter.GetBytes(fileLength); //그걸 바이트화 시킨다.
socket.Send(fileBuffer); //파일 크기를 보낸다.
// 파일 이름 크기 전송
int fileNameLength = (int)filename.Length; //파일 이름의 크기를 구한다.
fileBuffer = BitConverter.GetBytes(fileNameLength); //그걸 바이트화 시킨다.
socket.Send(fileBuffer); //파일 이름의 크기를 보낸다
// 파일 이름 전송
fileBuffer = Encoding.UTF8.GetBytes(filename); //파일의 이름을 바이트화시킨다.
socket.Send(fileBuffer); //파일 이름을 보낸다
// 파일 전송
int count = fileLength / 1024 + 1;
BinaryReader reader = new BinaryReader(fileStream);
for (int i = 0; i < count; i++)
{
fileBuffer = reader.ReadBytes(1024);
socket.Send(fileBuffer);
}
reader.Close();
socket.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
딱 봐도, 군더더기가 많아도 너무 많습니다.
이 예제를 "재현 가능한 최소한의 프로젝트"로 줄인다면 어떻게 될까요? 그러니까 결국,
질문자가 원하는 것은 Progress Bar의 진행과 텍스트 박스의 내용이 한 박자 틀리다는 것이므로, 그냥 다음과 같이 줄이면 됩니다.
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
this.progressBar1.Maximum = 5;
for (int i = 1; i <= 5; i++)
{
this.progressBar1.Value = i;
this.label1.Text = i.ToString();
this.label1.Update();
Thread.Sleep(1500);
}
}
}
}
그리곤, bin, obj, .vs 등의 폴더는 삭제하고
기본 프로젝트 파일만 포함해,
│ WindowsFormsApp1.sln
│
└───WindowsFormsApp1
│ App.config
│ Form1.cs
│ Form1.Designer.cs
│ Form1.resx
│ Program.cs
│ WindowsFormsApp1.csproj
│
└───Properties
AssemblyInfo.cs
Resources.Designer.cs
Resources.resx
Settings.Designer.cs
Settings.settings
질문과 함께 첨부해 주면 됩니다. 이로써 문제도 명확해지고, 답변을 하려는 사람들도 질문자의 정확한 질문 범위를 파악할 수 있습니다. 위와 같이 줄여서 문제를 단순화시키면, 꼭 제 사이트가 아니더라도 다른 질문/답변 게시판에 올리면, (답변하는 사람들은) 설령 저 문제의 답변을 모르는 사람들까지도 프로젝트를 내려받아 이거저거 테스트하면서 답을 찾아내려고 할 수도 있습니다.
그리곤, 다음과 같이 변경하면 된다고 알려줄 것입니다.
private void button1_Click(object sender, EventArgs e)
{
this.progressBar1.Maximum = 5;
Thread t = new Thread(threadFunc);
t.IsBackground = true;
t.Start();
}
void threadFunc()
{
for (int i = 1; i <= 5; i++)
{
this.progressBar1.Invoke(
(System.Action)(() =>
{
this.progressBar1.Value = i;
this.label1.Text = i.ToString();
}), null);
Thread.Sleep(1500);
}
}
얼마나 간단합니까?
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]