i have this code, which has an animation and some other statements (they are like resetting the TextBoxs and other UI Controls).
what is happening is that the animation is starting with the statements at he same time, it looks weird that the users sees that the text in the TextBoxs suddenly disappers, so i want these other statements to wait some time (the animation duration) before being executed.
//this is an Animation that takes me back to the home view
switching = (Storyboard)this.FindResource("view_Copy1");
switching.Begin();
//I want these statments to wait until the animation is finished
st1.Children.Clear();
st2.Children.Clear();
st3.Children.Clear();
st4.Children.Clear();
name.Text = lname.Text = fname.Text = mname.Text = sex.Text =
bplace.Text = bday.Text = idcard.Text = socal.Text = region.Text = location.Text =
telephone.Text = mobile1.Text = mobile2.Text = email1.Text = email2.Text =
ctype.Text = cyear.Text = emergname1.Text = emergrelation1.Text = emergloc1.Text =
emergphone1.Text = emergname2.Text = emergrelation2.Text = emergloc2.Text = emergphone2.Text = "";
region.Items.Clear();
New.IsEnabled = true;
id = "";
bt_del.Visibility = Visibility.Collapsed;
There is a Completed event that you can use and on completion you execute your code.
switching.Begin();
switching.Completed += Storyboard_Completed;
...
private void Storyboard_Completed(object sender, EventArgs e)
{
// Your code to execute after the animation completed.
}
Related
I'm trying to make a "Choose your own adventure game" in C#, and having a problem with the buttons. I have 6 buttons, and I want to reuse them for the different options on each "page" of the story. Program Window
As an example, I was using this for btn1.
void page1(){
txtStory.Text = "Can you sneak past the enemy?";
btn1.Click += (sender, args) => {
luckCheck();
if (lucky) {page2(); clearButtons();}
else {page3(); clearButtons();}
};
}
void luckCheck(){
int luckTest = gen.Next(1,12);
if (luckTest <= playerLuck) {lucky = true;}
else {lucky = false;}
}
void clearButtons(){
btnN.Text = "";
btnS.Text = "";
btnE.Text = "";
btnW.Text = "";
btn1.Text = "";
btn2.Text = "";
btn3.Text = "";
btn4.Text = "";
btn5.Text = "";
btn6.Text = "";
}
void page2(){
txtStory.Text = "Lucky!";
}
void page3(){
txtStory.Text = "Not Lucky!";
}
Even though it has moved on to another method, if clicked, btn1 still repeats the same command from page1. Is there a way to stop this from happening? Like clearing the memory for the button or something.
The problem you're having is that page1() is attaching an anonymous handler method, which is never detached, so each time you call it, you are just adding more and more handlers.
But this design is going to get out of hand very quickly once you start adding lots of pages.
Consider a different design, where you have a Page class to represent each page. That class would have the story as a property on it, and a collection of possible Choices (rather than hard-coding 6 of them). Choice is a class with a Description string property and an Action method to be executed if it is used.
Your application can hold the CurrentPage property, and your Choice's Action will need to be able to navigate by changing that page. Etc, etc...
Hope this helps.
P.S. extra tip. Rather than returning the result of the luckCheck method on a class field, consider redesigning it to return the result:
bool luckCheck(){
int luckTest = gen.Next(1,12);
return luckTest <= playerLuck;
}
Without going into details as to how the code above could be structured better, here is a solution using C# 7. Note how a local function is assigned to handle the event then unassigned - using Delegate Type Inference
void page1(){
txtStory.Text = "Can you sneak past the enemy?";
void Btn1Click(object s, EventArgs ev)
{
luckCheck();
if (lucky) {page2(); clearButtons(); btn1.Click -= Btn1Click; }
else {page3(); clearButtons();}
};
btn1.Click += Btn1Click;
void luckCheck(){
int luckTest = gen.Next(1,12);
if (luckTest <= playerLuck) {lucky = true;}
else {lucky = false;}
}
void clearButtons(){
btnN.Text = "";
btnS.Text = "";
btnE.Text = "";
btnW.Text = "";
btn1.Text = "";
btn2.Text = "";
btn3.Text = "";
btn4.Text = "";
btn5.Text = "";
btn6.Text = "";
}
void page2(){
txtStory.Text = "Lucky!";
}
void page3(){
txtStory.Text = "Not Lucky!";
}
I have an application running with the database. When I load a tables in the datagridview, my form freezes. How to ensure the smooth load animation during loading tables?
I run two threads for animation and load data into the tables, but the animation still does not always work.
private volatile bool threadRun;
private void UpdateTab()
{
// Create panel for animation
Panel loadingPanel = new Panel();
// Label, where the text will change
Label loadingLabel = new Label();
loadingLabel.Text = "Loading";
loadingPanel.Controls.Add(loadingLabel);
this.Controls.Add(loadingPanel);
// thread loading animation
threadRun = true;
Task.Factory.StartNew(() =>
{
int i = 0;
string labelText;
while (threadRun)
{
Thread.Sleep(500);
switch (i)
{
case 0:
labelText = "Loading.";
i = 1;
break;
case 1:
labelText = "Loading..";
i = 2;
break;
default:
labelText = "Loading...";
i = 0;
break;
}
loadingLabel.BeginInvoke(new Action(() => loadingLabel.Text = labelText));
}
});
// thread update DataGridView
Thread update = new Thread(ThreadUpdateTab);
update.Start();
}
private void ThreadUpdateTab()
{
// SQL Query...
myDataGridView1.Invoke(new Action(() => myDataGridView1.DataSource = myDataSet1.Tables[0]));
// ...
myDataGridView10.Invoke(new Action(() => myDataGridView10.DataSource = myDataSet10.Tables[0]));
threadRun = false;
}
When the form is frozen, it means the UI thread is too busy and so even if you try to show a loading animation, it will not animate. You should load data asynchronously.
You can have an async method which returns Task<DataTable> like the GetDataAsync method which you can see in this post. Then call it in an async event handler. In the event handler, first show the loading image, then load data asynchronously and then hide the loading image.
You can simply use a normal PictureBox showing a gif animation as loading control. Also you may want to take a look at this post to show a transparent loading image.
public async Task<DataTable> GetDataAsync(string command, string connection)
{
var dt = new DataTable();
using (var da = new SqlDataAdapter(command, connection))
await Task.Run(() => { da.Fill(dt); });
return dt;
}
private async void LoadDataButton_Click(object sender, EventArgs e)
{
loadingPictureBox.Show();
loadingPictureBox.Update();
try
{
var command = #"SELECT * FROM Category";
var connection = #"Your Connection String";
var data = await GetDataAsync(command, connection);
dataGridView1.DataSource = data;
}
catch (Exception ex)
{
// Handle Exception
}
loadingPictureBox.Hide();
}
I have a button named submit_Button which has the following code in the OnClicked event:
Thread thread = new Thread(new ThreadStart(heavyWork));
thread.Start();
heavyWork function code :
private void heavyWork()
{
DisableUI();
string Name = Name_textBox.Text;
celebrityName = Name.Replace(" ", "+");
string queryURL = "http://stackoverflow.com";
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(queryURL);
request.Method = "GET";
// make request for web page
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader htmlSource = new StreamReader(response.GetResponseStream());
string htmlStringSource = string.Empty;
htmlStringSource = htmlSource.ReadToEnd();
response.Close();
//var regex = new Regex(#"<FONT class=""result"">(.*?)</FONT>");
var regex = new Regex(#"<span class=""kno-a-v"">(.*?)</span>");
var match = regex.Match(htmlStringSource);
var result = match.Groups[1].Value;
result = HttpUtility.HtmlDecode(result);
MessageBox.Show(result);
EnableUI();
}
// Functions
private void DisableUI()
{
celebrityName_textBox.IsEnabled = false;
submit_Button.IsEnabled = false;
infoType_listBox.IsEnabled = false;
preloader_Image.Visibility = Visibility.Visible;
}
private void EnableUI()
{
celebrityName_textBox.IsEnabled = true;
submit_Button.IsEnabled = true;
infoType_listBox.IsEnabled = true;
preloader_Image.Visibility = Visibility.Hidden;
}
When I run the application, then press the button, the application crashes immediately!
What's happening ? I tried to use BackgroundWorker Instead, but when I can worker.RunWorkerAsync() nothing happens ( the worker doesn't start ).
DisableUI() is changing the state of UI controls on a thread which is not the UI thread. This is disallowed in WPF, because it is a single threaded UI framework, and only allows you to change controls on something called the UI thread. Dispatcher.Invoke()/BeginInvoke() is your friend.
Good MSDN article on using the Dispatcher
You are going to want to do any UI related stuff on the UI thread, you can do this with the dispatcher, like so:
private void heavyWork()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(DisableUI));
//rest of method
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(EnableUI));
}
When using Background Worker your code should look like something like this:
Notice that when you start worker.RunWorkerAsync() method BackgroundWorkerDoWork is called
BackgroundWorker _backgroundWorker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_backgroundWorker.DoWork += BackgroundWorkerDoWork;
_backgroundWorker.RunWorkerCompleted += BackgroundWorkerRunWorkerCompleted;
void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
//DU STUFF HERE
}
void BackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
/DO STUFF HERE LIKE HIDE / SHOW / ENABLE/ DISABLE buttons
}
Here is the architecture of my app. I have a Perl script that is called using the method below on click of a button.
ProcessStartInfo psStartInfo = new ProcessStartInfo("perl.exe");
psStartInfo.Arguments = paramStr;
psStartInfo.UseShellExecute = false;
psStartInfo.RedirectStandardOutput = true;
psStartInfo.RedirectStandardError = true;
psStartInfo.CreateNoWindow = false;
ps.StartInfo = psStartInfo;
ps.Start();
string os = ps.StandardOutput.ReadToEnd();
The above code executes successfully. There is a text file which is generated using the Perl script I fired in the above mentioned code. I have to read that file and show everything in that in my WPF textbox. That Perl file is updated after every 5 to 10 secs. I use the below mentioned code to read the file and show it in my textbox.
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 30);
dispatcherTimer.Start();
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
txtExecLog.Text = "";
if (File.Exists("C:\\FlashAuto\\Execution_Logs\\log.txt"))
{
File.Copy("C:\\FlashAuto\\Execution_Logs\\log.txt", "C:\\FlashAuto\\Temp\\log.txt", true);
TextReader readLogs = new StreamReader("C:\\FlashAuto\\Temp\\log.txt");
string line = readLogs.ReadLine();
while (line != null)
{
txtExecLog.Text += "\n" + line;
line = readLogs.ReadLine();
txtExecLog.ScrollToEnd();
}
CountLines = txtExecLog.LineCount - 1;
readLogs.Close();
// Forcing the CommandManager to raise the RequerySuggested event
txtExecLog.ScrollToEnd();
CommandManager.InvalidateRequerySuggested();
readLogs.Dispose();
}
else
{
txtExecLog.Text += "log file not found at: "+DateTime.Now.ToString();
}
}
Here is the problem:
Reading and writing to the textbox is successfully done but there is a huge chunk of memory that is eaten up by my app. If I disable the logging, memory usage is optimal.
Im trying to load a dataGridView which takes some time, so ive come up with an idea of hiding the datagridview and put an image over the top which says Loading... when finished, the image goes away and the datagrid reappears. Ive tried to do this using threading but having no luck.
Can somebody tell me if i am approaching this in the right way?
Label loadingText = new Label();
PictureBox loadingPic = new PictureBox();
private void TelephoneDirectory_Load(object sender, EventArgs e)
{
dataGridView1.Visible = false;
Thread i = new Thread(LoadImage);
i.Start();
Thread t = new Thread(LoadData);
t.Start();
}
void LoadImage()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(LoadImage));
}
else
{
loadingPic.Image = Properties.Resources.ReportServer;
loadingPic.Location = new Point(0, 0);
loadingPic.Name = "loadingPic";
loadingPic.Dock = DockStyle.Fill;
loadingPic.SizeMode = PictureBoxSizeMode.CenterImage;
loadingText.Text = "Loading, please wait...";
loadingText.Name = "loadingText";
loadingText.TextAlign = ContentAlignment.MiddleCenter;
loadingText.Size = new Size(this.Size.Width, 30);
loadingText.Font = new System.Drawing.Font("Segoe UI", 9);
loadingText.Location = new Point(0, (this.Size.Height / 2 + 10));
this.Controls.AddRange(new Control[] { loadingPic, loadingText });
loadingText.BringToFront();
}
}
private void LoadData()
{
if (dataGridView1.InvokeRequired)
{
dataGridView1.Invoke(new MethodInvoker(this.LoadData));
}
else
{
DirectorySearcher sea = null;
DirectoryEntry dir = null;
DirectoryEntry d = null;
string domainname = System.DirectoryServices.ActiveDirectory.Domain.GetComputerDomain().Name;
domainname = domainname.Replace(".", ",DC=");
try
{
dir = new DirectoryEntry();
dir.Path = "LDAP://DC=" + domainname;
sea = new DirectorySearcher(dir);
sea.Filter = "(&(objectClass=user)(extensionAttribute1=1)(telephoneNumber=*))";
sea.PageSize = 2000;
SearchResultCollection res = sea.FindAll();
foreach (SearchResult result in res)
{
//DO ALL MY STUFF
dataGridView1.Rows.Add(row);
}
}
catch { }
LoadDataComplete();
}
}
void LoadDataComplete()
{
PictureBox loadingGraphic = this.Controls["loadingPic"] as PictureBox;
Label LoadingLabel = this.Controls["loadingText"] as Label;
DataGridView dataGrid = this.Controls["dataGridView1"] as DataGridView;
dataGrid.Visible = true;
LoadingLabel.Visible = false;
LoadingLabel.Dispose();
loadingGraphic.Visible = false;
loadingGraphic.Dispose();
dataGridView1.Sort(dataGridView1.Columns[0], ListSortDirection.Ascending);
dataGridView1.ClearSelection();
}
Spawning threads for this might not be the best idea, you could use ThreadPool or BackgroundWorker
Loading image should be fast enough that you could just do it synchronously, so make sure you actually need to do some operation on the other thread before you actually need it.
Ask yourself questions like: what if actually my image will load later then my dataTable? ("but it loads faster every time I checked" is not an valid argument when talking about threads)
At the beginning of your methods you are using .Invoke which basically means "wait for UI thread and invoke my code on it synchronously" which bombards your whole idea.
Try something like this:
Load image synchronously
Use ThreadPool to load your DataTable in it, but without using .Invoke
When it's loaded and you need to interact with UI -> then put your code in .Invoke()
Pseudocode coud look like this:
private void TelephoneDirectory_Load(object sender, EventArgs e)
{
dataGridView1.Visible = false;
LoadImage();
ThreadPool.QueueUserWorkItem(new WaitCallback(o => LoadData()));
}
void LoadData()
{
//...Do loading
//but don't add rows to dataGridView
if (dataGridView1.InvokeRequired)
{
//Invoke only the ui-interaction code
dataGridView1.Invoke(new MethodInvoker(this.LoadDataComplete));
}
}
void LoadDataComplete()
{
foreach (SearchResult result in res)
{
//DO ALL MY STUFF
//If do all my stuff is compute intensive and doesn't require UI,
//put it before Invoke() (like here)
dataGridView1.Rows.Add(row);
}
//Rest of code
}