i've noticed something within VS/C# thats a bit of a grey area for me? was hoping someone could explain. i've followed guides online to set up a Treeview Control, and copied and pasted the code that said it handled the label and put it through certain validations as you'll see below, however the desired result is not quite there.
What is happening:
it seems as though the code is only checking the edit, and not the label? because if the label isn't changed, i start to see the messageboxes come up even though, the value isn't null, the length is > 0 AND does NOT contain special characters.. which leads me to beleive its only checking the new value?
private void TreeSlabs_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
{
if (e.Label != null)
{
if (e.Label.Length > 0)
{
if (e.Label.IndexOfAny(new char[] { '#', '.', ',', '!' }) == -1)
{
// Stop editing without canceling the label change.
e.Node.EndEdit(false);
}
else
{
/* Cancel the label edit action, inform the user, and
place the node in edit mode again. */
e.CancelEdit = true;
MessageBox.Show("Invalid tree node label.\n" +
"The invalid characters are: '#','.', ',', '!'",
"Node Label Edit");
e.Node.BeginEdit();
}
}
}
else
{
/* Cancel the label edit action, inform the user, and
place the node in edit mode again. */
e.CancelEdit = true;
MessageBox.Show("Invalid tree node label.\nThe label cannot be blank",
"Node Label Edit");
e.Node.BeginEdit();
}
}
could someone verify my theory?
What would be the better way around this code to test the value? i know the values going into the tree are "validated" as per the conditions above.. so really if the label hasn't changed, it should still allow the edit but it wont...?
Any advice, as always greatly appreciated.
Many Thanks in advance to SO community.
Jack
Related
I have a TextBox where a user is meant to enter a product code that looks like this:
XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
What I'm trying to do is automatically placing the dashes in the right place as the user is typing, so that they only have to enter the actual code itself (similar to what's done in various pieces of software).
The problems I'm encountering have got to do with the position of the cursor as the user is typing.
My current solution (partially working) is as the following:
private void textBoxProductKey_KeyPress(object sender, KeyPressEventArgs e)
{
if (char.IsControl(e.KeyChar))
{
return;
}
if (!char.IsLetterOrDigit(e.KeyChar) || textBoxProductKey.Text.Length == 29)
{
e.Handled = true;
return;
}
var cursorPosition = textBoxProductKey.SelectionStart;
string text;
if (cursorPosition == 0)
{
text = e.KeyChar + textBoxProductKey.Text;
}
else if (cursorPosition == textBoxProductKey.Text.Length)
{
text = textBoxProductKey.Text + e.KeyChar;
}
else
{
text = textBoxProductKey.Text.Insert(cursorPosition, e.KeyChar.ToString());
}
text = Regex.Replace(text, "-", "");
text = Regex.Replace(text, ".{5}", "$0-");
textBoxProductKey.Text = text.Length <= 29 ? text : text.Substring(0, 29);
textBoxProductKey.SelectionStart = cursorPosition / 6 == (cursorPosition + 1) / 6
? cursorPosition + 1
: cursorPosition + 2;
e.Handled = true;
}
The two problems with my current solution are
It doesn't properly handle the delete/backspace keys where potentially the dashes need to be deleted/inserted in different locations and the cursor position re-calculated
It doesn't work properly if the cursorPosition == textBoxProductKey.Text.Length.
The latter is very easy to fix, but I'm struggling with the former and also feel like the code is a bit convoluted.
How can I more easily achieve this (note the plan is to continue using a TextBox object).
EDIT
I am aware of the MaskedTextBox control, I do not want to use a MaskedTextBox, I want to use a TextBox.
What I am trying to do is not outside the realms of possibility for a TextBox, even if there are "easier" ways to accomplish this with other controls.
Also I feel like this is a great question for teaching people how to work with the cursor in a TextBox (among other things probably).
Have you considered using the MaskedTextBox control? It might be more appropriate.
The MaskedTextBox is designed precisely for this type of requirement and would answer your original question.
You may find it more difficult to implement this functionality yourself with a normal TextBox. It's also worth weighing up which solution is more likely to be be bug-free and easier to maintain.
It may helpful if you could to explain to us why you would prefer use a TextBox over a MaskedTextBox.
Continuing our conversation, save your current caret position, parse the whole text as if it's new, and return the caret to its original position.
That way you handle deleting as well as copy-pasting
FastColoredTextbox is an user-control that can be downloaded in this url, it looks like this:
Its an amazing control but only can select one word when doubleclicking on the text, can't hold the mouse to select more words, so it only selects the entire current word over the mouse pointer even if you try to move the mouse cursor to left or right to select more text.
I have not found any information explaining the problem, and all of the official example projects has this problem.
Nobody means how to make an AutoWordSelection equivalent of a default TextBox for a FastcoloredTextbox control, but even the most important thing is:
How to select just more than one word with the mouse?
UPDATE:
#mostruash answer is very instructive but in all this time I could not carry out the modifications by myself.
I need a huge help from a C# programmer to carry out this task, my knowledge of C# is very low and the modifications that I made to the source did not work (don't compiled), I went back to the original user-control source to not end up spoiling more. I hate to say this but this time I need the job done, this source is too much for me.
If I'm requesting for too much then maybe with the necesary extended instructions of a C# developer, explaining how to accomplish this step by step, maybe I could carry it out by myself.
UPDATE
A video that demostrates the problem:
https://www.youtube.com/watch?v=Cs2Sh2tMvII
UPDATE
Another demo, I show what the FastColoredTextBox can't do but I would like to do like every other text-editor can do:
I've checked the source code of the project. Dragging is cancelled if a double click occurs and SelectWord is called.
You could modify the source code to include the feature that you request. (https://github.com/PavelTorgashov/FastColoredTextBox). In that case:
You must trace selections that start with double clicks.
Instead of calling SelectWord function, use the Selection class and draggedRange attribute to mark the selected word in OnMouseMove.
You also must handle deselection of words in OnMouseMove.
You must also select spaces between words in OnMouseMove.
The double click is handled in the code piece below:
if (!isLineSelect)
{
var p = PointToPlace(e.Location);
if (e.Clicks == 2)
{
mouseIsDrag = false; //Here, drag is cancelled.
mouseIsDragDrop = false;
draggedRange = null; //Drag range is nullified
SelectWord(p); //SelectWord is called to mark the word
return;
}
if (Selection.IsEmpty || !Selection.Contains(p) || this[p.iLine].Count <= p.iChar || ReadOnly)
OnMouseClickText(e);
else
{
mouseIsDragDrop = true;
mouseIsDrag = false;
}
}
EDIT:
This may require a lot of work to accomplish. So maybe you should use another tool/library. I have not studied the whole source code so there will probably be additional steps to those provided above.
For example, to trace double clicks you can do the following:
Define a class variable/property in FastColoredTextbox.cs: bool isDoubleClick.
Set it to true in OnMouseDown under if(e.Clicks == 2) condition. Set it to false in all other conditions.
Set it to false in OnMouseClick or OnMouseUp or in other relevant mouse event handlers.
That way you will know if series of mouse events had started with a double click event or not. Then you would act accordingly in OnMouseMove because that is where you (un)mark characters or (un)mark words.
LAST WORDS OF CAUTION:
The author of that project did not include any inline comments or any other means of documentation so you will be studying the code line by line to understand what each function/part does.
Add the following statement between Line 5276 and line 5277 in the class FastColoredTextBox.cs:
SelectWord(p);
mouseIsDrag = true; // here
return;
Note that implementing the ultimate behavior would require a good bunch of coding. Whereas the workaround mentioned above might satisfy your needs.
As #mostruash points out in his answer, that is the place where author cancels the mouse drag. Not sure why he deliberately prevents this feature. Only he knows.
if (e.Clicks == 2)//Line 5270
{
mouseIsDrag = false;
mouseIsDragDrop = false;
draggedRange = null;
SelectWord(p);
return;
}
I didn't read whole code, and I have no reason to do it. I just checked quickly and removed them. And it works as you expect.
if (e.Clicks == 2)//Line 5270
{
//Comment or remove completely.
//mouseIsDrag = false;
//mouseIsDragDrop = false;
//draggedRange = null;
SelectWord(p);
return;
}
Note: Am not sure this breaks something else, I've not tested. At least that works. Test it yourself.
My solution is a bit tweaky, but seems to work at first glance.
You have to make some changes in the Code:
Add mouseIsWholeWordSelection flag and a Range variable which can store the initial selected range after double click (best after line 100, I guess):
private bool mouseIsWholeWordSelection;
private Range mouseIsWholeWordSelectionBaseRange;
Change the selection code for double click event as stated above and extend it a bit (line 5222):
if (e.Clicks == 2)
{
//mouseIsDrag = false;
mouseIsDragDrop = false;
mouseIsWholeWordSelection = true;
//draggedRange = null;
SelectWord(p);
mouseIsWholeWordSelectionBaseRange = Selection.Clone();
return;
}
Add evaluation of dragging event for recreating selection (line 5566):
else if (place != Selection.Start)
{
if (mouseIsWholeWordSelection)
{
Selection.BeginUpdate();
var oldSelection = Selection.Clone();
SelectWord(place);
if (Selection.End >= mouseIsWholeWordSelectionBaseRange.End)
{
Selection.Start = (mouseIsWholeWordSelectionBaseRange.Start > Selection.Start) ? mouseIsWholeWordSelectionBaseRange.Start : Selection.Start;
Selection.End = mouseIsWholeWordSelectionBaseRange.End;
}
else if (Selection.Start < mouseIsWholeWordSelectionBaseRange.End)
{
Selection.Start = new Place(Selection.End.iChar, Selection.End.iLine);
Selection.End = mouseIsWholeWordSelectionBaseRange.Start;
}
Selection.EndUpdate();
DoCaretVisible();
Invalidate();
}
else
{
Place oldEnd = Selection.End;
Selection.BeginUpdate();
if (Selection.ColumnSelectionMode)
{
Selection.Start = place;
Selection.ColumnSelectionMode = true;
}
else
Selection.Start = place;
Selection.End = oldEnd;
Selection.EndUpdate();
DoCaretVisible();
Invalidate();
}
return;
}
Add at every place where isMouseDrag is being set to false:
isMouseWholeWordSelection = false;
And there you go.
I've got a form with multiple text boxes which are file paths for the program to import data from. Currently they are checked for non-zero length by the following:
//this code imports the files required by the user, as specified in the
//file path text boxes
private void btImport_Click(object sender, EventArgs e)
{
bool hasPath = false;
foreach (TextBox box in this.gbPaths.Controls.OfType<TextBox>().Where(tb => tb.Text.Length > 0))
{
hasPath = true;
//import code
}//end foreach
if (!hasPath)
{
MessageBox.Show("You must enter at least one file path.");
}//end if
}//end import code
What I'm wondering is can I replace the //import code part with something like:
if(tb.Name = "txtAvF") then...
or similar, or do I have to do it outside of the foreach loop? Thanks in advance. Let me know if I need to clarify anything.
If you want to check to see if the TextBox is one of the ones on the form (which I think you are), then you are == which (taken from MSDN)
the operator == tests for reference equality by determining if two references indicate the same object
So this is what you're looking for:
if(box == textBox1 && !string.IsNullOrEmpty(box.Text))
{
// Import Textbox1
}
else if(box == textBox2 && !string.IsNullOrEmpty(box.Text))
{
// Import Textbox2
}
else if (box == textBox3....)
You should do it inside of the loop. Like this:
if (box.Name == "txtAvF")
box.Text = "What you want";
But setting hasPath inside the loop just holds state for your last path. You should also move MessageBox code inside loop.
The hasPath assignment seems correct to me; it's set for any one text box, and if not set at the end of the loop, a message is displayed. This rhymes well with the text so displayed. Moving the MessageBox call into the loop would cause the dialog box to never be displayed (or errenously displayed), at least as the code is implemented now, since the OfType<>().Where() guarantees that all text boxes iterated over have at least some content.
(I would add this as a comment to #Xaqron but don't have the necessary reputation yet.)
I am currently trying to create some basic word processor features in a WPF project. I am using a RichTextBox and am aware of all of the EditingCommands (ToggleBold, ToggleItalic...ect.). The thing I am stuck on is allowing the user to change the fontsize and font face like in MS Office where the value changes for only the selected text and if there is no selected text then the value will change for the current caret position. I have come up with a decent amount of code to get this to work, but am having problems with the no selected text thing. Here is what I am doing for the RichTextBox.Selection.
TextSelection text = richTextBox.Selection;
if (text.IsEmpty)
{
//doing this will change the entire word that the current caret position
//is on which is not the desire/expected result.
text.ApplyPropertyValue(RichTextBox.FontSizeProperty, value);
}
else
//This works as expected.
text.ApplyPropertyValue(RichTextBox.FontSizeProperty, value);
So my question is how should I go about doing this? Is there a better/more convenient way to do this? One thought I had was that I would need to insert a new Inline into the Paragraph but I couldn't figure out how to do that. Any help is appreciated. Thank you.
Full disclaimer: This is an exact repost of this question from 7 months ago. I found it while searching for a solution to the exact same problem, however that question wasn't answered and I hope that someone will be able to answer it now nevertheless.
Try this:
private void ChangeTextProperty(DependencyProperty dp, string value)
{
if (mainRTB == null) return;
TextSelection ts = mainRTB.Selection;
if (ts.IsEmpty)
{
TextPointer caretPos = mainRTB.CaretPosition;
TextRange tr = new TextRange(caretPos, caretPos);
tr.Text = " ";
tr.ApplyPropertyValue(dp, value);
}
else
{
ts.ApplyPropertyValue(dp, value);
}
}
I hope it does the trick
You can either explicitly re-set the focus to the RichTextBox by calling its Focus() method after applying the new value to the TextRange, or better yet, make the toolbar items not focusable. for example, if you have a combobox for font sizes:
<ComboBox x:Name="FontSizeSelector" Focusable="False" />
Then you can just use the original code, without the need for calling Focus():
text.ApplyPropertyValue(RichTextBox.FontSizeProperty, value);
OK, just found the answer:
private void ChangeTextProperty(DependencyProperty dp, string value)
{
if (mainRTB == null) return;
TextSelection ts = richTextBox.Selection;
if (ts!=null)
ts.ApplyPropertyValue(dp, value);
richTextBox.Focus();
}
I have a treeview with nodes like this: "Foo (1234)", and want to allow the user to rename the nodes, but only the Foo part, without (1234). I first tried to change the node text in BeforeLabelEdit like this:
private void treeView1_BeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
{
e.Node.Text = "Foo";
}
But when I click the node to edit it, "Foo (1234)" appears in the textbox.
Okay, then let's try something else.
I set treeView1.LabelEdit to false, and then do the following:
private void treeView1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (treeView1.SelectedNode == treeView1.GetNodeAt(e.Location))
{
treeView1.SelectedNode.Text = "Foo";
treeView1.LabelEdit = true;
treeView1.SelectedNode.BeginEdit();
}
}
}
And then in AfterLabelEdit, I set LabelEdit back to false.
And guess what? This doesn't work either. It changes the node text to "Foo" but the edit textbox does not appear.
Any ideas?
Thanks
Finally I have found a solution to this on CodeProject. Among the comments at the bottom, you will also find a portable solution.
Heh - I struck that one a few years back. I even left a suggestion on Connect (vote for it!) to allow the label to be changed in BeforeLabelEdit.
One option (in WinForms - it's a different story in WPF) is to use custom painting for your TreeNodes so that the actual label is still "Foo" and you custom draw the " (1234)" after it. It's a bit of a pain to get right though.