ОБНОВЛЕНИЕ: Я вставил весь источник для г-на Крафта. Сказав это, это, вероятно, мое первое настоящее приложение WinForms, поэтому, пожалуйста, проанализируйте и проанализируйте, если вам это нравится. Кроме того, чтобы сделать критику менее болезненным для меня, я должен отметить, что я написал это приложение очень быстро, поэтому у меня есть задача рефакторинга и улучшения дизайна для него, назначенного мне :)Быть способным отлаживать приложение WinForms и избегать использования графического интерфейса от замораживания
Так что просто немного фона. Я веб-разработчик .NET, но мне нужно было создать настольное приложение для наших разработчиков с некоторыми обычными задачами, которые они выполняют каждый день, чтобы ускорить производительность. Ответ на это может быть очень очевиден, но я едва коснулся WinForms, поэтому, пожалуйста, со мной.
Я создал приложение C# WinForms (.NET 2.0) и все работает, но у меня есть некоторые проблемы. Когда я изначально написал это, пользовательский интерфейс был слабым, поэтому, естественно, я решил создать поток, чтобы сделать вещи более текучими. Это сработало, однако я не могу отлаживать приложение из-за этого потока. Приложение прерывается в VS.NET, когда я его отлаживаю. VS.Net замерзает. Я продолжаю читать о потоке пользовательского интерфейса, но понятия не имею, как получить к нему доступ, и я не понимаю его в стороне от того факта, что, скорее всего, что-то связанное с UI должно быть в этой теме.
Я включил фрагмент кода, показывающий, что я сделал, чтобы заставить пользовательский интерфейс останавливаться от замораживания.
using System;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Text.RegularExpressions;
namespace Acuity.Tools
{
/// <summary>
/// A form for managing Acuity development databases.
/// </summary>
public partial class DeveloperTools : Form
{
#region Public Constructors
public DeveloperTools()
{
InitializeComponent();
}
#endregion
#region Private Constants
private const string Scripts1To50Region = "#region 1 to 50#";
private const string Scripts51AndUpRegion = "#region 51+#";
private const string DefaultSanitizedPassword = "t78";
private const string UseSanitizedPasswordTemplateVariable = "%%UseSanitizedPassword%%";
private const string DatabaseToRestoreTemplateVariable = "%%DatabaseToRestore%%";
private const string DatabaseToRestoreToTemplateVariable = "%%DatabaseToRestoreTo%%";
private const string SanitizedPasswordTemplateVariable = "%%sanitizedPassword%%";
private const string StarKeyTemplateVariable = "%%StarKey%%";
#endregion
#region Private Methods
/// <summary>
/// Starts the database restore.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RestoreDatabaseButton_Click(object sender, EventArgs e)
{
RestoreDatabaseButton.Enabled = false;
LogViewer.Text = string.Empty;
UseWaitCursor = true;
// Need to spawn a thread or else the UI freezes.
Thread restoreDatabase = new Thread(new ThreadStart(RestoreDatabase));
restoreDatabase.Start();
}
/// <summary>
/// Starts the database restore.
/// </summary>
private void RestoreDatabase()
{
error = false;
// TODO: Database restores fine, but it looks like an error is logged here. Not sure why.
// for now I just ignore the errors. Will need to look into it.
if (RestoreDatabaseCheckbox.Checked)
{
RestoreProductionDbToDevelopmentDb();
}
// TODO: The error reporting is a little buggy at the moment, It logs errors in a couple of cases even though
// things work. Perhaps John can look at his dbobjects.vbs script at some point to see why an error is logged.
if (!error)
{
if (GenerateDatabaseObjectsCheckbox.Checked)
{
GenerateUpdatedDatabaseObjects();
}
if (!error)
{
if (RunScriptsCheckbox.Checked)
{
RunVersioningScripts();
}
}
}
RestoreCompleted();
}
/// <summary>
///
/// </summary>
private void RestoreCompleted()
{
if (error)
{
MessageBox.Show("There were some errors!", "Error");
}
else
{
MessageBox.Show("Done!", "Done");
}
UseWaitCursor = false;
RestoreDatabaseButton.Enabled = true;
LogViewer.Enabled = true;
}
/// <summary>
/// Runs versioning scripts on a development database.
/// </summary>
private void RunVersioningScripts()
{
LogViewer.Text += "Running versioning scripts..." + Environment.NewLine;
FileInfo versioningScriptsBatchFileName = new FileInfo(BatchFileForVersioningScripts.Text);
FileInfo scriptFileName = new FileInfo(applicationRootFolderPath + @"\Temp\" + (Guid.NewGuid()).ToString() + ".bat");
StringBuilder fileContents = new StringBuilder();
string drive = Regex.Match(versioningScriptsBatchFileName.Directory.FullName, "^[a-zA-Z]{1,1}").Value;
fileContents.Append(drive + ":\n");
fileContents.AppendFormat("cd \"{0}\"\n", versioningScriptsBatchFileName.Directory.FullName);
fileContents.AppendFormat("\"{0}\" %1 %2 %3 %4 \"{1}\"\n", versioningScriptsBatchFileName.Name, applicationRootFolderPath + @"\Temp\DBObjects");
File.WriteAllText(scriptFileName.FullName, fileContents.ToString());
using (Process process = new Process())
{
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = scriptFileName.FullName;
process.StartInfo.Arguments = string.Format(
"{0} {1} {2} {3}",
SqlLogin.Text,
SqlPassword.Text,
DatabaseServer.Text,
DatabaseToRestoreTo.Text
);
process.OutputDataReceived
+= new DataReceivedEventHandler(OutputDataHandler);
process.ErrorDataReceived += new DataReceivedEventHandler(ErrorDataHandler);
process.Exited += new EventHandler(ProcessExited);
process.EnableRaisingEvents = true;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
while (!process.HasExited)
{
Console.WriteLine("Still running");
Thread.Sleep(3000);
}
}
scriptFileName.Delete();
LogViewer.Text += "Finished running versioning scripts.";
}
/// <summary>
/// Restores a production database to a development database.
/// </summary>
private void RestoreProductionDbToDevelopmentDb()
{
string restoreScriptTemplate = File.ReadAllText(applicationRootFolderPath + @"\Resources\RestoreProdDBToDevelopment.template");
string restoreScript = restoreScriptTemplate.Replace(DatabaseToRestoreTemplateVariable, DatabaseToRestore.Text).Replace(DatabaseToRestoreToTemplateVariable, DatabaseToRestoreTo.Text);
restoreScript = restoreScript.Replace(UseSanitizedPasswordTemplateVariable, UseSanitizePassword.Checked ? "1" : "0");
if (UseSanitizePassword.Checked)
{
restoreScript = restoreScript.Replace(SanitizedPasswordTemplateVariable, SanitizedPassword.Text ?? DefaultSanitizedPassword);
}
restoreScript = restoreScript.Replace(StarKeyTemplateVariable, UseCustomStarKey.Checked ? StarKey.Text : DatabaseToRestore.Text + "Pwd");
string restoreScriptFileName = applicationRootFolderPath + @"\Temp\" + (Guid.NewGuid()).ToString() + ".sql";
File.WriteAllText(restoreScriptFileName, restoreScript);
using (Process process = new Process())
{
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = "sqlcmd";
process.StartInfo.Arguments = string.Format(
"-U {0} -P {1} -S {2} -d {3} -i \"{4}\" -k -b",
SqlLogin.Text,
SqlPassword.Text,
"super-secret-database-server",
"master",
restoreScriptFileName
);
process.OutputDataReceived
+= new DataReceivedEventHandler(OutputDataHandler);
process.ErrorDataReceived += new DataReceivedEventHandler(ErrorDataHandler);
process.Exited += new EventHandler(ProcessExited);
process.EnableRaisingEvents = true;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
while (!process.HasExited)
{
Console.WriteLine("Still running");
Thread.Sleep(3000);
}
}
File.Delete(restoreScriptFileName);
}
/// <summary>
/// Regenerates all database objects that will be updated in the development database.
/// </summary>
private void GenerateUpdatedDatabaseObjects()
{
LogViewer.Text += string.Format(
"Generating updated database objects from folder {0}",
DatabaseObjectsFolderPath.Text) + Environment.NewLine;
FileInfo updateDatabaseObjectsScriptFileName = new FileInfo(applicationRootFolderPath + @"\Resources\dbobjscripts.vbs");
FileInfo scriptFileName = new FileInfo(string.Format(@"{0}\Temp\{1}.bat", applicationRootFolderPath, Guid.NewGuid()));
StringBuilder fileContents = new StringBuilder();
string drive = Regex.Match(scriptFileName.Directory.FullName, "^[a-zA-Z]{1,1}").Value;
fileContents.Append(drive + ":\n");
fileContents.AppendFormat("cd \"{0}\"\n", scriptFileName.Directory.FullName);
string dateToStartGeneratingFrom = string.Format("{0:yyyy/MM/dd}", GenerateDBObjectsDate.Value);
LogViewer.Text += "Database objects will be updated starting from " + dateToStartGeneratingFrom;
fileContents.AppendFormat("\"{0}\" {1} \"{2}\"\n", updateDatabaseObjectsScriptFileName.FullName, dateToStartGeneratingFrom, DatabaseObjectsFolderPath.Text);
File.WriteAllText(scriptFileName.FullName, fileContents.ToString());
using (Process process = new Process())
{
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = scriptFileName.FullName;
process.OutputDataReceived
+= new DataReceivedEventHandler(OutputDataHandler);
process.ErrorDataReceived += new DataReceivedEventHandler(ErrorDataHandler);
process.Exited += new EventHandler(ProcessExited);
process.EnableRaisingEvents = true;
process.Start();
Cursor.Current = Cursors.WaitCursor;
process.BeginErrorReadLine();
process.BeginOutputReadLine();
while (!process.HasExited)
{
Console.WriteLine("Still running");
Thread.Sleep(3000);
}
}
scriptFileName.Delete();
LogViewer.Text += "Finished generating updated database objects." + Environment.NewLine;
}
/// <summary>
/// Raised when a database restore process has exited.
/// </summary>
/// <param name="sender">A <see cref="Process"/> that is exiting.</param>
/// <param name="e">A <see cref="EventArgs"/>.</param>
private void ProcessExited(object sender, EventArgs e)
{
Console.WriteLine("Exiting");
}
/// <summary>
/// Catches the redirected error stream for Yui Compressor.
/// </summary>
/// <param name="sender">A <see cref="Process"/> that is currently executing.</param>
/// <param name="e">A <see cref="DataReceivedEventArgs"/>.param>
private void YuicErrorDataHandler(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Data))
{
error = true;
CompressionLogViewer.Text += e.Data + Environment.NewLine;
}
}
/// <summary>
/// Catches the redirected error stream.
/// </summary>
/// <param name="sender">A <see cref="Process"/> that is currently executing.</param>
/// <param name="e">A <see cref="DataReceivedEventArgs"/>.param>
private void ErrorDataHandler(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Data))
{
error = true;
LogViewer.Text += e.Data + Environment.NewLine;
}
}
/// <summary>
/// Catches the redirected output stream for Yui Compressor.
/// </summary>
/// <param name="sender">A <see cref="Process"/> that is currently executing.</param>
/// <param name="e">A <see cref="DataReceivedEventArgs"/>.param>
private void YuicOutputDataHandler(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Data))
{
CompressionLogViewer.Text += e.Data + Environment.NewLine;
}
}
/// <summary>
/// Catches the redirected output stream.
/// </summary>
/// <param name="sender">A <see cref="Process"/> that is currently executing.</param>
/// <param name="e">A <see cref="DataReceivedEventArgs"/>.param>
private void OutputDataHandler(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Data))
{
LogViewer.Text += e.Data + Environment.NewLine;
if (e.Data.ToLower().Contains("login failed"))
{
error = true;
return;
}
}
}
/// <summary>
/// Closes the application via the File->Exit menu item.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ExitToolStripMenuItem_Click(object sender, EventArgs e)
{
Close();
}
/// <summary>
/// Raised when the Browse... button for selecting the database objects
/// folder is clicked.
/// </summary>
/// <param name="sender">A <see cref="Button"/> control.</param>
/// <param name="e">The <see cref="EventArgs" />.</param>
private void DatabaseObjectsFolderPath_Click(object sender, EventArgs e)
{
SelectFolderDialog.ShowDialog();
DatabaseObjectsFolderPath.Text = SelectFolderDialog.SelectedPath;
}
/// <summary>
/// Raised when the Browse... button for selecting a batch file to run versioning scripts
/// is clicked.
/// </summary>
/// <param name="sender">A <see cref="Button"/> control.</param>
/// <param name="e">The <see cref="EventArgs" />.</param>
private void BatchFileForVersioningScripts_Click(object sender, EventArgs e)
{
batchFileDialog.ShowDialog();
BatchFileForVersioningScripts.Text = batchFileDialog.FileName;
}
/// <summary>
/// Raised when the Run Versioning Scripts checkbox is checked/unchecked.
/// </summary>
/// <param name="sender">A <see cref="Checkbox" /> control.</param>
/// <param name="e">The <see cref="EventArgs"/>.</param>
private void RunScriptsCheckbox_CheckedChanged(object sender, EventArgs e)
{
DatabaseObjectsGroupBox.Enabled = RunScriptsCheckbox.Checked;
SqlLogin.Enabled = RunScriptsCheckbox.Checked;
SqlPassword.Enabled = RunScriptsCheckbox.Checked;
DatabaseToRestoreTo.Enabled = RunScriptsCheckbox.Checked;
}
/// <summary>
/// Raised when the Generate Database Objects checkbox is checked/unchecked.
/// </summary>
/// <param name="sender">A <see cref="Checkbox" /> control.</param>
/// <param name="e">The <see cref="EventArgs"/>.</param>
private void GenerateDatabaseObjectsCheckbox_CheckedChanged(object sender, EventArgs e)
{
BatchFileGroupBox.Enabled = GenerateDatabaseObjectsCheckbox.Checked;
}
/// <summary>
/// Raised when the Restore Database checkbox is checked/unchecked.
/// </summary>
/// <param name="sender">A <see cref="Checkbox" /> control.</param>
/// <param name="e">The <see cref="EventArgs"/>.</param>
private void restoreDatabaseCheckbox_CheckedChanged(object sender, EventArgs e)
{
DatabaseToRestore.Enabled = RestoreDatabaseCheckbox.Checked;
}
/// <summary>
/// Opens a log file to select a log file for analysis.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SelectLogFile_Click(object sender, EventArgs e)
{
openLogFileDialog.ShowDialog();
AnalyzeLogFile(openLogFileDialog.FileName);
}
/// <summary>
/// Analyzes a log file to see if there are any errors.
/// </summary>
/// <param name="fileName"></param>
private void AnalyzeLogFile(string fileName)
{
if (!string.IsNullOrEmpty(fileName))
{
using (Stream file = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
if (file != null)
{
using (StreamReader sr = new StreamReader(file))
{
string text = sr.ReadToEnd();
sr.Close();
MatchCollection matches = errorLocator.Matches(text);
StringBuilder sb = new StringBuilder();
if (0 < matches.Count)
{
foreach (Match match in matches)
{
sb.AppendLine(match.Groups["error"].Value);
}
ErrorsFound.Text = sb.ToString();
}
else
{
ErrorsFound.Text = "No errors found in log file!";
}
}
file.Close();
}
}
}
}
/// <summary>
/// Reloads the error log.
/// </summary>
private void RefreshErrorLog()
{
AnalyzeLogFile(openLogFileDialog.FileName);
}
private void RefreshLogFile_Click(object sender, EventArgs e)
{
RefreshErrorLog();
}
private void BrowseFileToCompress_Click(object sender, EventArgs e)
{
selectJSOrCssFile.ShowDialog();
FileToCompress.Text = selectJSOrCssFile.FileName;
}
private void StarUbcNickTesting(object sender, EventArgs e)
{
selectYuicFileName.ShowDialog();
YuicFilePath.Text = selectYuicFileName.FileName;
}
private void BrowseFileToCompressTo_Click(object sender, EventArgs e)
{
selectJSOrCssFile.ShowDialog();
FileToCompressTo.Text = selectJSOrCssFile.FileName;
}
private void GenerateCompressedFile_Click(object sender, EventArgs e)
{
Thread startCompression = new Thread(new ThreadStart(CompressedFile));
startCompression.Start();
}
private void CompressedFile()
{
error = false;
CompressionLogViewer.Text = "";
if (string.IsNullOrEmpty(YuicFilePath.Text))
{
CompressionLogViewer.Text += "You need to specify the path to Yui Compressor" + Environment.NewLine;
return;
}
if (string.IsNullOrEmpty(FileToCompress.Text))
{
CompressionLogViewer.Text += "You need to select a file to compress." + Environment.NewLine;
return;
}
if (string.IsNullOrEmpty(FileToCompressTo.Text))
{
CompressionLogViewer.Text += "You need to select a file to compress to." + Environment.NewLine;
return;
}
CompressionLogViewer.Text = string.Format(
"Compressing file {0} to {1}",
FileToCompress.Text,
FileToCompressTo.Text
) + Environment.NewLine;
using (Process process = new System.Diagnostics.Process())
{
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = "java";
process.StartInfo.Arguments = string.Format(
"-jar \"{0}\" -o \"{1}\" \"{2}\"",
YuicFilePath.Text,
FileToCompressTo.Text,
FileToCompress.Text
);
process.OutputDataReceived
+= new DataReceivedEventHandler(YuicOutputDataHandler);
process.ErrorDataReceived += new DataReceivedEventHandler(YuicErrorDataHandler);
process.Exited += new EventHandler(ProcessExited);
process.EnableRaisingEvents = true;
process.Start();
Cursor.Current = Cursors.WaitCursor;
process.BeginErrorReadLine();
process.BeginOutputReadLine();
while (!process.HasExited)
{
Console.WriteLine("Still running");
Thread.Sleep(3000);
}
if (!error)
{
CompressionLogViewer.Text += "The file has been compressed.";
}
}
}
private void BrowseForVersioningScriptsFolder_Click(object sender, EventArgs e)
{
SelectFolderDialog.ShowDialog();
if (!string.IsNullOrEmpty(SelectFolderDialog.SelectedPath))
{
VersioningScriptsFolderPath.Text = SelectFolderDialog.SelectedPath;
}
BindFileTreeView();
}
private void BindFileTreeView()
{
if (!string.IsNullOrEmpty(VersioningScriptsFolderPath.Text) &&
Directory.Exists(VersioningScriptsFolderPath.Text))
{
string[] versioningScriptFiles = Directory.GetFiles(VersioningScriptsFolderPath.Text, "*.sql");
fileTreeView.Nodes.Clear();
fileTreeView.CheckBoxes = true;
SelectAll.Checked = true;
foreach (string fullFileName in versioningScriptFiles)
{
TreeNode node = new TreeNode(Path.GetFileName(fullFileName));
node.Checked = true;
node.SelectedImageKey = fullFileName;
fileTreeView.Nodes.Add(node);
}
}
}
private void GenerateVersioningScriptsBatchFile_Click(object sender, EventArgs e)
{
VersioningScriptsLogViewer.Text = string.Empty;
if (string.IsNullOrEmpty(VersioningScriptsFolderPath.Text))
{
VersioningScriptsLogViewer.Text += "Please select a versioning scripts folder.";
return;
}
DirectoryInfo versioningScriptFolder = new DirectoryInfo(VersioningScriptsFolderPath.Text);
FileInfo versioningScriptsBatchFile = new FileInfo(string.Format(@"{0}\Update{1}.bat", versioningScriptFolder.FullName, versioningScriptFolder.Name));
// Remove existing update batch file if any.
if (versioningScriptsBatchFile.Exists)
{
versioningScriptsBatchFile.Delete();
VersioningScriptsLogViewer.Text += "Removing existing versioning scripts batch file " + versioningScriptsBatchFile.FullName + Environment.NewLine;
}
VersioningScriptsLogViewer.Text += "Generating versioning scripts batch file " + versioningScriptsBatchFile.FullName + Environment.NewLine;
script1To50 = new StringBuilder();
script51AndUp = new StringBuilder();
foreach (TreeNode versioningScript in fileTreeView.Nodes)
{
if (versioningScript.Checked)
{
AddVersioningScriptToBatchFile(new FileInfo(versioningScript.SelectedImageKey));
}
}
string templateFile = File.ReadAllText(applicationRootFolderPath + @"\Resources\VersioningScriptsBatchFile.template");
string newBatchFileContents = templateFile.Replace(Scripts1To50Region, script1To50.ToString());
newBatchFileContents = newBatchFileContents.Replace(Scripts51AndUpRegion, script51AndUp.ToString());
File.WriteAllText(versioningScriptsBatchFile.FullName, newBatchFileContents);
Я не вижу ничего очевидного; и у меня нет проблем с зависанием VS на меня с небольшим кодом, который вы предоставили. Вы должны уметь поставить точку останова в методе RestoreDatabase, и она остановится на нем, когда выполнение ударит по нему. Возможно, если вы разместили код в методе RestoreDatabase. –
В качестве примечания вы также можете запускать потоки в WinForms с помощью BackgroundWorker. – Powerlord
Г-н Крафт, я вложил полный источник. – nickytonline