I'm coding a simple text editor using Windows Forms. As in many editors, when the text changes the title bar displays an asterisk next to the title, showing that there is unsaved work. When the user saves, this goes away.
However, there is a problem. This is handled in the change event of the main text box. But this gets called too when a file is opened or the user selects "New file", so that if you open the editor and then open a file, the program says that there are unsaved changes. What is a possible solution?
I thought of having a global variable that says whether the text changed in a way that shouldn't trigger the asterisk, but there has to be a better way.
before loading data to a textbox, unassociate first the eventhandler for change
uxName.TextChanged -= uxName_TextChanged;
uxName.Text = File.ReadAllText("something.txt");
uxName.TextChanged += uxName_TextChanged;
This is a horrible solution, but every time the text change event fires, compare the value of the textbox to some variable, and if they are different store the contents on the textbox in a variable and add the asterisk. When the method is invoked via the New File dialog or any other such event that is NOT changing the text, the asterisk won't appear.
This is not a viable solution for a real text editor since the memory would quickly get out of hand on even medium-sized files. Using a finger tree or whatever data structure text editors use to compare "versions" of the text is the only real efficient solution, but the premise is the same.
http://scienceblogs.com/goodmath/2009/05/finally_finger_trees.php
Below the second picture he mentions the use of finger trees in text editors to implement an extremely cheap "undo" feature, but I'm sure you can see the validity of the tree for your problem as well.
There are no global variables in C#. You should have such an variable as an instance variable in your form (or better yet, in a model for which your form is a view), and that is perfectly fine.
This is a very simple and stupid solution. I would use a MVP design pattern for this but here the fastest and simple solution:
//Declare a flag to block the processing of your event
private bool isEventBlocked = false;
private void OnTextChanged(object sender, EventArgs e)
{
if(!isEventBlocked)
{
//do your stuff
}
}
private void OnNewFile() //OR OnOpenFile()
{
try
{
isEventBlocked = true;
CreateFile();
}
catch
{
//manage exception
}
finally
{
isEventBlocked = false;
}
}
Related
I'm sorry if this seems incredibly obvious or a very much commonly asked question, but I've been searching and looking over posts for a while now and i still can't seem to get it.
I'm just getting into learning C# and I set myself a little project, making a word processor around a richtextbox control with a few extra features.
I'm currently just adding in the ability to 'Find & Replace' text, and the below code is working when used on the same form as the rich text box control.
richTextBox1.Rtf = richTextBox1.Rtf.Replace("bob", "bill");
I don't want to use a dialog box or something similar, i'm coming direct from our old friend VB6 though, so i'm not sure if they still even exist as such, so i'm making an external form that acts sort of like a dialog box, where i'd like the user to be able to enter the text to look for and replace and then press okay, and be sent back to the main form, sounds simple huh, probably is, i'm not sure what i'm missing...
private void findReplaceToolStripMenuItem_Click(object sender, EventArgs e)
{
Form3 AboutBox = new Form3();
AboutBox.ShowDialog();
}
I've tried my best at implementing a few of the answers I've read over here, in one of them i managed to be able to control form1 but only if i opened a new instance of it with form1.show(); after the code, which is kind of useless in what i'm trying to achieve.
I've set the richTextBox1.Modifiers to Public, but I'm still scratching my head over this one.
Instead of making the RichTextBox public, I'd add a property to the other form that returns the text from that control, like this:
public class SearchForm : Form
{
public string SearchTerm
{
get { return richTextBox1.Text; }
}
...
When the user closes the "search" form, you can get the search term by referencing the property:
private void findReplaceToolStripMenuItem_Click(object sender, EventArgs e)
{
string searchTerm;
using (var searchForm = new SearchForm()) // used 'using' to dispose the form
{
searchForm.ShowDialog();
searchTerm = searchForm.SearchTerm;
}
// do something with searchTerm
}
You'll find this makes maintenance more manageable too. Changing the names of controls in one form shouldn't require you to make changes in any other form that uses them.
I am using ScintillaNET to make a basic IntelliSense editor. However, I have a problem when I call _editor.CallTip.Show("random text") in the AutoCompleteAccepted Event.
If I type pr for example, and scroll and select printf in the drop-down list, it goes to my AutoCompleteAccepted event and when I call the CallTip.Show, the rest of the word does not get added (however, without that CallTip code, the rest of the word is filled).
So, if I typed pr then it stays pr and I get my CallTip. How do I make sure the rest of the word gets inserted AND the CallTip shows?
Is the AutoCompleteAccepted Event not the right place to call it? If so, where should I call the CallTip.Show so that it works side-by-side with my AutoComplete?
Finally figured it out! The AutoCompleteAccepted Event isn't the right place to put CallTip.Show
What is going on is the fact that when AutoCompleteAccepted Event is called and you add text to the ScintillaNET control, it takes time for the UI to update and so, when you call to show the CallTip, it interferes with the text being inserted into the control.
The better way to do it is to call CallTip.Show in the TextChanged event, as they you know that the text has been inserted when the AutoCompleteAccepted Event was called.
It would now look something like this:
String calltipText = null; //start out with null calltip
...
private void Editor_TextChanged(object sender, EventArgs e)
{
if (calltipText != null)
{
CallTip.Show(calltipText); //note, you may want to assign a position
calltipText = null; //reset string
}
...
}
...
private void Editor_AutoCompleteAccepted(object sender, AutoCompleteAcceptedEventArgs e)
{
if (e.Text == "someThing")
{
/* Code to add text to control */
...
calltipText = "someKindOFText"; //assign value to calltipText
}
}
That is essentially what can be done to ensure the AutoComplete fills correctly and you get the CallTip to show.
Just note, that the CallTip MAY end up in unintended places, so it is recommended to set the value of where you want the CallTip to show up
I have a Windows Phone 8 project that converts values (i.e.: Celsius to Fahrenheit). There are two TextBox UI elements, one of which is read-only. The user can change the first TextBox to input the value to be converted. He can also press a button to "swap" the two TextBoxes so that he can do the reverse conversion. When the user presses the button, the value from the second TextBox goes into the first TextBox (and vice versa). But it's not the user who changed the value, it's the code who did.
I asked around (on IRC) and researched the subject, but I am a beginner and couldn't understand most of what I have found.
I heard that a simple solution would be to use Data Bindings. I researched the subject, and from what I read, Data Bindings can't solve my problem (correct me if I'm wrong).
I also tried to create a subclass of TextBox, hoping that I could hook in some custom event to it and go further in that direction. But I did not understand how to link the custom TextBox to the UI (in XAML). The way I created the subclass is to just create a new class and add TextBox as the parent. I know there is a template in VS to create a new User Control, and I tried it, but I couldn't understand what I was doing (or what I was supposed to do).
So I have two questions: Am I looking at the problem from the right angle? If yes, how do I create a custom TextBox and link it to the UI? If not, how could I solve my problem?
If your question is how to distinguish if the text got changed by the user or by the code then its simple.
Assuming that when the user types something you'd like to perform method A but when the code changes the text you'd like to perform method B:
In both cases you will need to override the TextBox.TextChanged() event handler.
You will also need a flag variable to tell you if the swap button was pressed or not.
The event handler should be something like this:
{
if (swap_pushed)
{
Method_B();
swap_pushed = false;
}
else
{
Method_A();
}
}
And finally your event handler for swap Button.Click() should be like this:
{
swap_pushed = true;
}
Long time listener, first time caller here. I'm having a strange issue with the TextBox in WinRT C#/XAML that I hope someone may be able to help me with.
Basically, I'm working on creating a Custom Control that essentially requires a second TextBox to be a copy of the first, including showing the same Text, and showing the same Selected Text. Obviously for the Text requirement I simply respond to the TextChanged event on the first TextBox and set the Text of the second TextBox to the Text from the first, which works great.
For the Selected Text requirement I started with a similar solution, and my code for this is as follows:
void TextBox1_SelectionChanged(object sender, RoutedEventArgs e)
{
this.TextBox2.Select(this.TextBox1.SelectionStart, this.TextBox1.SelectionLength);
}
This seemed to work pretty well when initially used with a mouse:
But I'm having a problem when selecting text with Touch. I double-tap within the TextBox to create the first "anchor" as you do in Touch, then drag to begin the selection; but I only ever manage to select a single character normally before the selection stops. The TextBox doesn't lose focus exactly, but the behaviour is similar to that; the selection anchors disappear and I can't continue selecting anything unless I re-double-tap to start a new selection. If I remove the code to select text in TextBox2 then the Touch selection behaves perfectly in TextBox1.
I've been trying to fix this for a while and cannot, I'm not sure if I can get the desired behaviour with WinRT TextBoxes. Does anyone have any ideas? Or perhaps another way to implement a solution with two TextBoxes with this behaviour?
Thanks a lot.
So this is far from an answer, but discovered a few things that maybe will help you or others come up with a potential workaround. Apologies if these are things you've already seen and noted.
First, it's not the call to TextBox2.Select() that's the problem per se. This for instance, works fine for me
private void txt1_SelectionChanged(object sender, RoutedEventArgs e)
{
var start = TextBox1.SelectionStart;
var length = TextBox1.SelectionLength;
TextBox2.Select(3, 5);
}
unfortunately, using start and length versus the hard-coded 3 and 5, that is, the following, DOES NOT WORK:
private void txt1_SelectionChanged(object sender, RoutedEventArgs e)
{
var start = TextBox1.SelectionStart;
var length = TextBox1.SelectionLength;
TextBox2.Select(start, length);
}
I also discovered that I could select TWO characters if I started from the end, but only one from the beginning. That got me to thinking about dispatching the call to set the second selection:
private void txt1_SelectionChanged(object sender, RoutedEventArgs e)
{
var start = TextBox1.SelectionStart;
var length = TextBox1.SelectionLength;
Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low,
() => TextBox2.Select(start, length));
}
Now I can select 2 from the front and 3 and sometimes 4 from the back. Took it a step further, and was able to select as many as six or seven with a really fast swipe.
private void txt1_SelectionChanged(object sender, RoutedEventArgs e)
{
var start = TextBox1.SelectionStart;
var length = TextBox1.SelectionLength;
Dispatcher.RunIdleAsync((v) => Highlight());
}
public void Highlight()
{
TextBox2.Select(TextBox1.SelectionStart, TextBox1.SelectionLength);
}
Seems like the trick to working around this is not setting TextBox2 until whatever vestiges of the TextBox1 SelectionChanged event have completed.
This may be worth registering on Connect.
Mine is only a partial solution as well.
I did some debugging and noticed that the SelectionChanged event is fired throughout the text selection process. In other words, a single finger "swipe" will generate multiple SelectionChanged events.
As you found out, calling TextBox.Select during a text selection gesture affects the gesture itself. Windows seems to stop the gesture after the programmatic text selection.
My workaround is to delay as long as possible calling the TextBox.Select method. This does work well, except for one edge case. Where this method fails is in the following scenario:
The user begins a select gesture, say selecting x characters. The user, without taking their finger off the screen, pauses for a second or two. The user then attempts to select more characters.
My solution does not handle the last bit in the above paragraph. The touch selection after the pause does not actually select anything because my code will have called the TextBox.Select method.
Here is the actual code. As I mentioned above, there are multiple selection changed events fired during a single selection gesture. My code uses a timer along with a counter to only do the programmatic selection when there are no longer any pending touch generated selection changed events.
int _selectCounter = 0;
const int SELECT_TIMER_LENGTH = 500;
async private void TextBox1_SelectionChanged(object sender, RoutedEventArgs e)
{
// _selectCounter is the number of selection changed events that have fired.
// If you are really paranoid, you will want to make sure that if
// _selectCounter reaches MAX_INT, that you reset it to zero.
int mySelectCount = ++_selectCounter;
// start the timer and wait for it to finish
await Task.Delay(SELECT_TIMER_LENGTH);
// If equal (mySelectCount == _selectCounter),
// this means that NO select change events have fired
// during the delay call above. We only do the
// programmatic selection when this is the case.
// Feel free to adjust SELECT_TIMER_LENGTH to suit your needs.
if (mySelectCount == _selectCounter)
{
this.TextBox2.Select(this.TextBox1.SelectionStart, this.TextBox1.SelectionLength);
}
}
When you use MessageBox.Show() you have a selection of MessageBoxButtons to choose from. The buttons available are an enum, and give you options like "Yes No", "OK Cancel", etc.
When I am using, for instance, Norwegian message text the user still gets the English "Yes No".
Is there a way to change the text of the buttons (in C#) so that the language is correct? Can I override the text, or set the current locale in some way so that I can have "Ja Nei" instead of "Yes No"?
I do not want to rely on installing a .NET language pack at my client.
There is no native support for this in .NET (as far as I know, anyway; please correct me if I'm wrong, anyone). I did come across this CodeProject article, that seem to do the trick with some message hooking and P/Invoke:
http://www.codeproject.com/KB/miscctrl/Localizing_MessageBox.aspx
Usually messagebox buttons (as all of Windows) honor the currently set UI language for Windows. So if you've got an English installation and can't change languages (MUI versions or Ultimate for Vista/7) you're out of luck.
You could implement a messagebox yourself but I'd beg you not to. Simple things like common hotkeys for the buttons, having the ability to use Ctrl+Ins to copy the contents, etc. are the ones I miss the most when people start reinventing square wheels.
I don't think it is possible, but refer to the MSDN article MessageBox.Show Method. You may get some ideas. You can change the text in the message box. What about creating your own message box (new form) and displaying them?
You can create a panel (pnlExitMode), with property Visible=false, and put info text as well as buttons (btnYes, btnNo) labeled YES and NO (with button captions in "your" language) onto this panel, then place required Yes/No- actions into the button event handling routines.
At decision time (in my case: warning if ini file not yet written when closing application) set panel to Visible. Panel with buttons will pop up.
Example code:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (bIniModified)
{
pnlExitMode.Visible = true; pnlExitMode.BringToFront();
e.Cancel = true;
}
}
private void btnYes_Click(object sender, EventArgs e)
{
SaveToIni();
pnlExitMode.Visible = false; bIniModified = false;
Application.Exit();
}
private void btnNo_Click(object sender, EventArgs e)
{
pnlExitMode.Visible = false; bIniModified = false;
Application.Exit();
}