How can I implement effective Undo/Redo into my WinForms application? - c#

I have a WinForm text editor.
I would like to be able to allow the user to undo and redo changes in the Rich Text Box, like they can in Microsoft Word.
I have spent the past week or so researching how to do this, and most results seem to be regarding graphics applications.
The standard richTextBox1.Undo(); gives disappointing results, as it undoes everything that the user has written.
Does anybody have any idea how I could implement effective undo/redo? Preferably one which undoes/redoes the action word-by-word as opposed to character-by-character.

This is a very basic idea, and I'm sure that many improvements could be made.
I would create a String Array and incrementally store the value of the RichTextBox (In the TextChanged event, under your own conditions) in the array. As you store the value, increment the value of a counter, say stackcount. When the user undoes, decrement the stackcount and set the RichTextBox.Text = array(stackcount). If they redo, then increment the value of the counter and set the value again. If they undo and then change the text, then clear all values onwards.
I am sure that many other people may have better suggestions/changes for this, so please post in comments and I will update, or edit it yourself!
Example in C#
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 RedoUndoApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public string[] RTBRedoUndo;
public int StackCount = 0;
public int OldLength = 0;
public int ChangeToSave = 5;
public bool IsRedoUndo = false;
private void Form1_Load(object sender, EventArgs e)
{
RTBRedoUndo = new string[10000];
RTBRedoUndo[0] = "";
}
private void undo_Click(object sender, EventArgs e)
{
IsRedoUndo = true;
if (StackCount > 0 && RTBRedoUndo[StackCount - 1] != null)
{
StackCount = StackCount - 1;
richTextBox1.Text = RTBRedoUndo[StackCount];
}
}
private void redo_Click(object sender, EventArgs e)
{
IsRedoUndo = true;
if (StackCount > 0 && RTBRedoUndo[StackCount + 1] != null)
{
StackCount = StackCount + 1;
richTextBox1.Text = RTBRedoUndo[StackCount];
}
}
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
if (IsRedoUndo == false && richTextBox1.Text.Substring(richTextBox1.Text.Length - 1, 1) == " ")//(Math.Abs(richTextBox1.Text.Length - OldLength) >= ChangeToSave && IsRedoUndo == false)
{
StackCount = StackCount + 1;
RTBRedoUndo[StackCount] = richTextBox1.Text;
OldLength = richTextBox1.Text.Length;
}
}
private void undo_MouseUp(object sender, MouseEventArgs e)
{
IsRedoUndo = false;
}
private void redo_MouseUp(object sender, MouseEventArgs e)
{
IsRedoUndo = false;
}
}
}

One way to do this is use the TextChanged event to periodically store the contents of richtextbox.text in an array or list, as a stack. When you undo, "pop the stack" and copy the most recent version on the stack into richtextbox.text.
TextChange can determine whether a change should be saved onto the stack, whether a new word, line, or character.

Related

Taking Value From Text Box Into Total (VS - C#)

I am new to the C# scene, so don't really have much knowledge - This is my first project.
I am looking to create a very basic calorie counter, which eventually will include other functions.
Here's what I have so far;
I want to know how to take the value from the text box on the click of the 'Add' button - Which adds to the total value (bottom right)
I'm looking for any tips/videos to help so anything is appreciated.
TIA
I made a simple implementation for you, you could refer to it:
using System;
using System.Windows.Forms;
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void textBox1_KeyUp(object sender, KeyEventArgs e)
{
bool Flag = Int32.TryParse(textBox1.Text, out int result);//Determine if it is a number
if (textBox1.Text == "")//If it is null, falg takes true and skips the next judgment
{ Flag = true; }
else if (!Flag)
{
MessageBox.Show("Input Error");
textBox1.Text = null;
}
}
private void button1_Click(object sender, EventArgs e)
{
Int32.TryParse(textBox1.Text, out int result);
int total =result + Convert.ToInt32(label3.Text);
label3.Text = total.ToString();
}
private void button2_Click(object sender, EventArgs e)
{
label3.Text = "0";
}
}
}

How to set value to textbox in c# from jagged array?

In the picture I want the save button to save the entries of customers to a jagged array and then when I press the show button the saved names should show into the text box below.
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 week5hw
{
public partial class Form1 : Form
{
string[][] str = new string[3][];
public Form1()
{
InitializeComponent();
}
public void Button1_Click(object sender, EventArgs e)
{
String names = textBox1.Text;
str[0] = new string[5];
str[0][0] = names;
}
private void Button2_Click(object sender, EventArgs e)
{
}
}
}
I still don't see how a jagged array applies here. You need to give more details about how the program is supposed to work.
At any rate, you need to declare an int variable to track how many of those spots have been used. That variable can also be used to determine which "row" in your jagged array to store the entered name in.
Might look something like this:
public partial class Form1 : Form
{
int count=0;
string[][] str = new string[3][];
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
label1.Text = "The number of free space in room is: " + str.Length;
textBox2.Multiline = true;
}
private void button1_Click(object sender, EventArgs e)
{
if (textBox1.Text.Trim().Length > 0)
{
if (count < str.Length)
{
str[count] = new string[] { textBox1.Text };
count++;
label1.Text = "The number of free space in room is: " + (str.Length - count);
textBox1.Clear();
textBox1.Focus();
}
else
{
MessageBox.Show("No space left!");
}
}
}
private void button2_Click(object sender, EventArgs e)
{
textBox2.Clear();
for(int i=0; i<count; i++)
{
textBox2.AppendText(str[i][0] + "\r\n");
}
}
}

Audiofiles are played with wrong speed

I am using NAudio in a C#-Application and my problem is, that the playback speed depends on the sampling rate. I used two Wav files that contain a 1000Hz sin wave with either Fs = 44100Hz and Fs = 88200 Hz. I checked the output signal of my soundcard with an oscilloscope. It turns out that my file with Fs = 44100 is over after half the expected time, whereas the output frequency is doubled (2kHz). When using the files in other players (e.g. windows media player, audacity) everything looks fine. When debugging I looked into the waveformat of these files and everything looked fine as well. I also varied the bit resolution resulting in no difference. I am not sure if i am just missing out of something.
I use this in another program where this problem first occured. Therefore I made a small rudimentary programm to see if I already made a mistake in my bigger application, the same problem exists there aswell.
It would be really great and highly appreciated, if someone could help me.
Kind regards
Leo
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;
using NAudio.Wave;
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
AsioOut asioOut;
AudioFileReader afr4;
AudioFileReader afr8;
AudioFileReader afr0;
MixingWaveProvider32 mwp;
int playingID = 0;
int channelID;
Boolean audioplaying = false;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
asioOut = new AsioOut();
afr4 = new AudioFileReader("../wavs/sin1000Hz_1.75s_16bit_44100Hz_ramp50ms.wav");
afr8 = new AudioFileReader("../wavs/sin1000Hz_1.75s_16bit_88200Hz_ramp50ms.wav");
afr0 = new AudioFileReader("../wavs/sin14607Hz_180sec_20msRamp_fs96000_24bit_mono_doppelt.wav");
mwp = new MixingWaveProvider32();
afr4.Volume = 1.0f;
afr8.Volume = 1.0f;
for (int i = 0; i < asioOut.DriverOutputChannelCount; i++) {
Console.WriteLine(i + ": " + asioOut.AsioOutputChannelName(i));
if (asioOut.AsioOutputChannelName(i).Equals("Analog 3 (1)")) {
channelID = i;
}
}
asioOut.ChannelOffset = channelID;
asioOut.Init(mwp);
asioOut.PlaybackStopped += OnPlaybackStopped;
}
//a button to play the Fs = 44100Hz File
private void button1_Click(object sender, EventArgs e)
{
if (audioplaying == false) {
mwp.AddInputStream(afr4);
afr4.Position = 0;
audioplaying = true;
playingID = 4;
asioOut.Play();
}
}
//a button to play the Fs = 88200Hz File
private void button2_Click(object sender, EventArgs e)
{
if (audioplaying == false) {
mwp.AddInputStream(afr8);
afr8.Position = 0;
audioplaying = true;
playingID = 8;
asioOut.Play();
}
}
//a button to play just another audiofile
private void button4_Click(object sender, EventArgs e)
{
if (audioplaying == false) {
mwp.AddInputStream(afr0);
afr0.Position = 0;
audioplaying = true;
playingID = 0;
asioOut.Play();
}
}
//after playback every Input gets removed again to setup for new playback
protected virtual void OnPlaybackStopped(object sender, EventArgs e) {
if (playingID == 4) {
mwp.RemoveInputStream(afr4);
} else if (playingID == 8) {
mwp.RemoveInputStream(afr8);
} else {
mwp.RemoveInputStream(afr0);
}
audioplaying = false;
}
private void button3_Click(object sender, EventArgs e)
{
asioOut.Stop();
}
}
}
You can't use MixingWaveProvider32 to mix together streams with different WaveFormat. You need to pick a single WaveFormat and pass that into the constructor. You should then only add items that have that WaveFormat although MixingWaveProvider32 isn't enforcing that when you only have one item at a time like you are doing

Refreshing a chart control

This one is driving me a bit crazy. Any help gratefully received. It's a simple program to receive temperature data from an Arduino based temp sensor, and display it in a graph control on a form. The program works fine, and parses the temp data frame a treat. However..... the graph object doesn't refresh, and the whole point is to show the data over time. I thought that the chart1.DataBind command that I put in forced a refresh. I am using Visual Studio 2013 Express. Any thoughts as to what I am doing wrong very much appreciated.
// Start of program.
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 serial_port_with_events_attempt_4
{
public partial class Form1 : Form
{
string RxString;
int RxRead;
int i;
int RxDec1;
int RxDec2;
int RxDec3;
float RxFloat;
float RxFloat2;
string locnString;
public Form1()
{
InitializeComponent();
}
private void buttonStart_Click(object sender, EventArgs e)
{
serialPort1.PortName = "COM8";
serialPort1.BaudRate = 9600;
serialPort1.Open();
if (serialPort1.IsOpen)
{
buttonStart.Enabled = false;
buttonStop.Enabled = true;
textBox1.ReadOnly = false;
}
}
private void buttonStop_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
serialPort1.Close();
buttonStart.Enabled = true;
buttonStop.Enabled = false;
textBox1.ReadOnly = true;
}
}
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
RxRead = serialPort1.ReadByte();
this.Invoke(new EventHandler(DisplayText));
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
}
private void DisplayText(object sender, EventArgs e)
{
if (RxRead == 126)
{
textBox1.AppendText(Environment.NewLine);
i = 0;
}
if (i< 23)
{
if (i == 7)
{
if (RxRead == 51) // 51 in this position means that the temp sensor is the one in the wooden box
{
textBox1.AppendText("Temperature in Nick's office = ");
locnString = ("Nick's office");
}
}
if (i == 17)
{
RxDec1 = RxRead - 48; // Read the tens unit
}
if (i == 18)
{
RxDec2 = RxRead - 48; // Read the units
}
if (i == 20)
{
RxDec3 = RxRead - 49; // read the decimal
}
if (i == 22)
{
RxFloat = ((RxDec1 * 10) + RxDec2);
RxFloat2 = RxDec3;
RxFloat2 = RxFloat2 / 10;
RxFloat = RxFloat + RxFloat2;
RxString = RxFloat.ToString();
if (RxFloat < 30 && RxFloat >20)
{
// Put the value in the main text box if it is not corrupt, (checking if the range is reasonable
textBox1.AppendText(RxString); // Frig about to get the reads in the right format and added together
// Add a new line into the temperature database
temperature1DataSetTableAdapters.Temp1TableAdapter temp1tableadapter = new temperature1DataSetTableAdapters.Temp1TableAdapter();
temp1tableadapter.Insert(DateTime.Now, RxFloat, locnString);
}
// Delete any old data.
temperature1DataSetTableAdapters.TempTableAdapter temp2tableadapter = new temperature1DataSetTableAdapters.TempTableAdapter();
temp2tableadapter.DeleteTempQuery();
// The above two lines work, but I need to amend to select on date TODO
chart1.DataBind();
}
}
i=i+1;
}
private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'temperature1DataSet.Temp' table. You can move, or remove it, as needed.
this.tempTableAdapter.Fill(this.temperature1DataSet.Temp);
}
}
}
Cheers,
Nick James

Timer intervals, Making 1 timer stop another timer -

Hey guys im trying to get a simple button masher up, What i want timer1 to do is mash keys in richtextbox1 for 30 seconds over and over, after 30 seconds Activate timer2, which will disable timer 1 and press keys in richtextbox 2 once, then wait 10 seconds and activate timer 1 again.
Im extremley new to c# but ive tried using timer 3 to stop timer 2 and start timer 1 again and it just messes it self up. The code ive tried is below. Any help apreciated...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
SendKeys.Send(richTextBox1.Text);
SendKeys.Send("{ENTER}");
}
private void button1_Click(object sender, EventArgs e)
{
timer1.Enabled = true;
timer2.Enabled = true;
}
private void timer2_Tick(object sender, EventArgs e)
{
SendKeys.Send(richTextBox2.Text);
SendKeys.Send("{ENTER}");
timer1.Enabled = false;
}
private void timer3_Tick(object sender, EventArgs e)
{
timer1.Enabled = true;
timer2.Enabled = false;
}
private void richTextBox2_TextChanged(object sender, EventArgs e)
{
}
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
}
}
}
I suggest to use just one timer, increment a state counter every second, and perform an action base on the current state.
public Form1()
{
this.InitializeComponent();
// Just to illustrate - can be done in the designer.
this.timer.Interval = 1000; // One second.
this.timer.Enable = true;
}
private Int32 state = 0;
private void timer_Tick(Object sender, EventArgs e)
{
if ((0 <= this.state) && (this.state < 30)) // Hit text box 1 30 times.
{
SendKeys.Send(this.richTextBox1.Text);
SendKeys.Send("{ENTER}");
}
else if (this.state == 30) // Hit text box 2 once.
{
SendKeys.Send(this.richTextBox2.Text);
SendKeys.Send("{ENTER}");
}
else if ((31 <= this.state) && (this.state < 40)) // Do nothing 9 times.
{
// Do nothing.
}
else
{
throw new InvalidOperationException(); // Unexpected state.
}
// Update state.
this.state = (this.state + 1) % 40;
}
The variant with two numeric up down controls.
public Form1()
{
this.InitializeComponent();
// Just to illustrate - can be done in the designer.
this.timer.Interval = 1000; // One second.
this.timer.Enable = true;
}
private Int32 state = 0;
private void timer_Tick(Object sender, EventArgs e)
{
Decimal n1 = this.numericUpDown1.Value;
Decimal n2 = this.numericUpDown2.Value;
if ((0 <= this.state) && (this.state < n1))
{
SendKeys.Send(this.richTextBox1.Text);
SendKeys.Send("{ENTER}");
}
else if (this.state == n1)
{
SendKeys.Send(this.richTextBox2.Text);
SendKeys.Send("{ENTER}");
}
else if ((n1 <= this.state) && (this.state < n1 + n2))
{
// Do nothing.
}
else
{
// Reset state to resolve race conditions.
this.state = 0;
}
// Update state.
this.state = (this.state + 1) % (n1 + n2);
}
If timer3 is running continuously, won't it start timer1 and stop timer2 at unpredictable times, without warning?
IOW, what starts and stops timer3?
As JustLoren pointed out, there might be a cleaner way to do this. Perhaps a single timer event and some controlling logic and flags, rather than trying to juggle three timers.

Categories

Resources