First thing first - I'm sorry for my poor English and I'm kinda new to this great community, so I'm really sorry if something is incorrect in my question.
In short - I've started with C# not so long ago (which means you find a lot of poor written code here) and now I'm aiming to create a dialogue system. Though I have some working sample, questions are:
How to make everything not this lame and improve the code;
Suggestions about improving performance;
Overall advices (maybe there is some more suitable tools for doing this);
Right now I use Finite-State Machine (FSM) as a general concept, so that every state is a dialogue scene. The last one is made of NPC quote and set of Player responds.
By now everything is pretty basic. Here I have my class for Player responds.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
namespace Testing
{
public class Player_Qoute : Label
{
public string Next_State { get; set; }//is used to tell, where to go next after choosing particular respond
}
It's inherited from Label and has one additional field - next stage number, which is added in function below:
private void NPC_Quote(string path, string specification, RichTextBox info)
{
StreamReader file = new StreamReader(path);//creating StremReader to read NPC quotes from .txt
string line = "";//Creating string variable to read from
try
{
while ((line = file.ReadLine()) != null)//readinf file line-by-line until the end
{
if (line.Contains(specification))//if line contains specified "tag"
{
line = line.Remove(0, specification.Length);//removing "tag" from line
info.Text += line + "\n";//adding NPC line to the output field
}
}
file.Close();
}
catch (Exception)
{
MessageBox.Show("Problem reading file");
}
}
This function parse through .txt file, searching for lines tagged "NPC_stage_n", where "n" - is a number of stage. This number is present at the end of every Player respond in .txt file and I put it in the "Next_Stage" field of Player_Quote objects.
The same idea is applied here, but now i dynamically create Player's responds (number of which varies from stage to stage). I'm facing some issues with appropriate placing of quotes on the GroupBox - sometimes they are missing a line or words, but I'm working on it:
void Quotes_Generation(string path, string specification, GroupBox parent)
{
parent.Controls.Clear();//deleting previous Player qoutes
int step = 0;//auxilary variable to separate quotes from each other by heigth
StreamReader file = new StreamReader(path);//StreamReader to read Player responds from .txt
string line = "";
while ((line = file.ReadLine()) != null)
{
if (line.Contains(specification))
{
Player_Qoute quote = new Player_Qoute();//inherited from Label;
quote.Name = "qoute_" + line.Remove(specification.Length, line.Length - specification.Length);
quote.Location = new Point(10, 20 + step);
quote.Size = new Size(360, 10);
quote.Text = line.Remove(0, specification.Length);//deleting "search tag" from text
quote.Text = quote.Text.Remove(quote.Text.Length-3, 3); //here we are deleting 3-digit number at the end of the string
//this number will show what is the next state of the dialogue if this Player respond is chosen.
quote.Next_State = line.Remove(0,line.Length - 3);//storing 3-digit number in Player_Quote property
using (Graphics g = CreateGraphics())//part of code which was borrowed from StackOverFlow and wasn't properly understood by me
{
SizeF size = g.MeasureString(quote.Text, quote.Font, 264);
quote.Height = (int)Math.Ceiling(size.Height);
quote.Text = quote.Text;
}
quote.MouseDown += new MouseEventHandler(this.Quote_Click);//creating event for choosing this respond
parent.Controls.Add(quote);//adding respond to GroupBox
step += (quote.Height+3);//increasing step
if (parent.Height < step)//enlarging GroupBox
{
parent.MaximumSize = new System.Drawing.Size(parent.Width, step + 50);
parent.Size = new System.Drawing.Size(parent.Width, step + 50);
}
}
}
file.Close();
}
And here is the Quote_Click event:
private void Quote_Click(object sender, EventArgs e)
{
Player_Qoute current = sender as Player_Qoute;//recognizing the sender
richTextBox1.Text += Player_Name + " - " + current.Text + "\n";//adding Player respond with Player name to RichTextBox
NPC_Quote(Application.StartupPath + "/Readme.txt", "NPC_stage_" + current.Next_State + ":", richTextBox1);//Adding new NPC line according to chosen respond
Quotes_Generation(Application.StartupPath + "/Readme.txt", "Player_stage_" + current.Next_State + ":", groupBox1);//refreshing responds according to previous actions
}
I'll appreciate all the advices!
Related
I am making this sum creator where user will have to type an answer using custom keyboard. and on check button click if answer is correct then new question is loaded.
My problem is after answering first question answer button reset to blank but when user types next answer, only one last alphabet is deleted (for example 5 from 15). and when i type 14 it shows 114 (1 from previously typed answer).
I need help to reset answer button text to blank.
I am using buttons because later i want to add more questions at the same time so user will have multiple answers to click and type.
Can anyone please help me on this? Also tell me if this is the right method to achieve what i want.
I am calling backspace function to delete previous answer and also setting text to blank.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Keyboard : MonoBehaviour
{
string word = null;
int wordIndex = -1;
string alpha = null;
string alpha2 = null;
public Text userAnswer1 = null;
public Text valueA, valueB;
public Text scoreCount;
private int a, b, answer1, score;
char[] nameChar = new char[5];
private void Start()
{
SumCreator();
}
public void nameFunc (string alphabet)
{
wordIndex++;
char[] keepchar = alphabet.ToCharArray();
nameChar[wordIndex] = keepchar[0];
alpha = nameChar[wordIndex].ToString();
word = word + alpha;
userAnswer1.text = word;
}
public void BackspaceFunction()
{
if (wordIndex >= 0)
{
wordIndex--;
alpha2 = null;
for (int i = 0; i < wordIndex + 1; i++)
{
alpha2 = alpha2 + nameChar[i].ToString();
}
word = alpha2;
userAnswer1.text = word;
}
}
public void SumCreator ()
{
a = Random.Range(0,15);
b = Random.Range(0,15);
answer1 = a + b;
valueA.text = a.ToString();
valueB.text = b.ToString();
scoreCount.text = "score " + score.ToString();
}
public void CheckAnswer()
{
Text buttonText = userAnswer1.GetComponentInChildren<Text>();
if (answer1 == int.Parse(userAnswer1.text))
{
score++;
// userAnswer1.text = string.Empty;
buttonText.text = string.Empty;
}
SumCreator();
}
}
I've edited my answer and removed the now irrelevant parts.
Once the button "Check" is clicked, first of all erase the text in the result textbox, then do the whole other logic.
To erase the text you can use next piece of code:
Text buttonText = buttonName.GetComponentInChildren<Text>();
buttonText.text = string.Empty;
You probably want to have this "buttonText" property as a global and get it once, at the start of the program instead of getting it every time the button is clicked. It won't do much difference in a small scale program, but it's a right way of thinking.
After checking your code a bit more, I can summarize your problem:
The whole logic of your program is flawed, there're many unnecessary complicated things which make it fail in several places. It is understandable, everybody goes through this stage, nothing to be ashamed or worried about. Either way it's my subjective opinion, which may be wrong.
Back to your code, all you have to do is update your result text, say "txtResult", once anything happens.
Once you click a number, do "txtResult += numberClicked".
Once you click backspace, remove last char of txtResult. Here is a question with many answers on how to do it, it's really simple.
Once you click "Check", in case it's the right number, set txtResult to empty.
Also, every time you update txtResult, you're supposed to update the UI too of course. Let's say you do it every time, it would be one line to update txtResult, and one line to update UI for each of the above 3 cases. So in total 6 lines. A check for an empty string while in "Backspace" function adds another line. My math could be wrong, but either way, it's quite short and simple approach, nothing too complicated.
You just lack relevant knowledge, otherwise you wouldn't be doing that nightmare in your Backspace function.
Regarding the "nameFunc" function, the whole 6 lines could be replaced with "txtResult += alphabet", isn't it? I'm not sure what you get in alphabet parameter, but either way, string is an array of chars, so you can also do "txtResult += alphabet[0]" instead of what you have there.
So, in total, you got it all right, the logic was right, you figured the main aspects. But you over complicated the whole thing. I believe you'll be fine after reading all this text, and wish you the best.
If you want to clear your Text object when you have succesfully entered your answer, you should not call your "BackSpace" function.
Just replace your code to this:
if (answer1 == int.Parse(userAnswer1.text))
{
score++;
userAnswer1.text = string.Empty;
This will clear the text element.
You could also look into using InputFields in Unity, which are designed for entering input and automatically support backspace and other keyboard functions.
If you do, make sure that you set the InputField's ContentType to either Integer Number or Decimal Number
I have a textBox named "textBoxCliente" and I want it to appear suggestions when I writte in there from the .txt file.
The suggestions that I want to appear from the .txt file are in the position 1 "parts[1]", each position are separated with the caracter "|".
My .txt file is this:
1|Rui|Lisboa|rui#hotmail.com|912345324|14/01/2000|89564352|Empresa
2|Henrique|Evora|henrique#hotmail.com|914445324|17/05/2001|55464352|Particular
3|Andre|Agueda|andre#hotmail.com|932415374|12/11/1996|23456743|Particular
4|Pedro|Aveiro|pedro#hotmail.com|965342163|30/03/2002|98645372|Empresa
My code is:
public partial class Vender : UserControl
{
public Vender()
{
InitializeComponent();
}
string dir = (Environment.CurrentDirectory + "/Bd/clientes.txt");
string[] sug = new string[File.ReadAllLines(Environment.CurrentDirectory +
"/Bd/clientes.txt").Count()];
private void textBoxCliente_TextChanged(object sender, EventArgs e)
{
carrSug();
for (int i = 0; i < sug.Length; i++)
{
textBoxCliente.AutoCompleteCustomSource.Add(sug[i]);
}
textBoxCliente.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
}
private void carrSug()
{
string[] lines = File.ReadLines(dir).ToArray();
int nLine = File.ReadAllLines(dir).Count();
for (int j = 0; j <= nLine - 1; j++)
{
string[] parts = lines[j].Split(new char[] { '|' });
sug[j] = parts[1];
}
}
}
What I did was using the "string[] sug" to save the values of the position 1 of each line and then use it to show the suggestions.
As a programmer, get better at reading carefully. Here is the documentation for AutoCompleteCustomSource:
Gets or sets a custom System.Collections.Specialized.StringCollection to use when the System.Windows.Forms.TextBox.AutoCompleteSource property is set to CustomSource.
Emphasis Mine
See the bolded part in the above, make sure you do that:
textBoxCliente.AutoCompleteSource = AutoCompleteSource.CustomSource;
Also, you do not need to do that every time the user types. The event handler textBoxCliente_TextChanged will be called every time the text changes. Instead, put the code in the constructor or in the form's load event.
Some Suggestions
Give your methods meaningful names. For example, carrSug() is not very meaningful. Plus it does not follow the C# coding conventions--it looks like Java. Also, keep the method cohesive. You are doing some parts of the suggestion in the carrSug() and then some of it you are doing in textBoxCliente_TextChanged. Here is a more meaningful method:
private AutoCompleteStringCollection clientSuggestions;
private void LoadClientSuggestions()
{
this.clientSuggestions = new AutoCompleteStringCollection();
string[] suggestionsFromFile = File.ReadLines("YourPath.txt").Select(x => x.Split('|').Skip(1).First()).ToArray();
this.clientSuggestions.AddRange(suggestionsFromFile);
}
The above method uses Ling so make sure to import: using System.Linq;
Here is how to use it (Put this code in your form's constructor or Load method):
this.LoadSuggestions();
this.textBoxCliente.AutoCompleteSource = AutoCompleteSource.CustomSource;
this.textBoxCliente.AutoCompleteCustomSource = this.clientSuggestions;
this.textBoxCliente.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
Why did I put the word Load in the method name? Because, it returns nothing so Load adds meaning.
Also, stop writing the same string multiple times:
"/Bd/clientes.txt"
Make that a constant so if you need to change it, you change it in one place.
How can i focus a Inline in a RichTextBox?
I Create a FlowDocument from a Text-File and load it in my richTextBox1
and mark one Inline after an other accordingly to a Button_click (be recreating the FlowDocument)
with this code:
richTextBox1.SelectAll();
richTextBox1.Selection.Text = "";
string text = System.IO.File.ReadAllText(file);
int iZeile = 0;
string[] split = text.Split(new string[] {"\r\n"},StringSplitOptions.None);
foreach (string s in split)
{
if (iZeile != 27)
{
paragraph.Inlines.Add(s + "\r\n"); // adds line added without marking
}
else
{
Run run = new Run(split[27]); // adds line with marking
run.Background = Brushes.Yellow;
paragraph.Inlines.Add(run);
paragraph.Inlines.Add("\r\n");
}
iZeile++;
}
FlowDocument document = new FlowDocument(paragraph);
richTextBox1.Document = new FlowDocument();
richTextBox1.Document = document;
Keyboard.Focus(richTextBox1);
}
I know its not.. perfect.
the Issue
It works so far but the problem that occurs is me Market Inline doesn't comes intoView. Is there a easy way to bring this Inline intoView?
The straightforward solution seemed to be FrameworkContentElement.BringIntoView() but after putting it in the code below it initially had no effect. As it turns out this is one of these timing issues (I've seen similar problems in WinForms) that can be solved by processing the outstanding Windows Messages. WPF has no direct equivalent of DoEvents() but there exists a well known substitute.
I placed this in a ButtonClick, changes marked with //**:
Paragraph paragraph = new Paragraph();
Inline selected = null; //**
richTextBox1.SelectAll();
richTextBox1.Selection.Text = "";
string text = System.IO.File.ReadAllText(#"..\..\MainWindow.xaml.cs");
int iZeile = 0;
string[] split = text.Split(new string[] { "\r\n" }, StringSplitOptions.None);
foreach (string s in split)
{
if (iZeile != 27)
{
paragraph.Inlines.Add(s + "\r\n"); // adds line added without marking
}
else
{
Run run = new Run(split[27]); // adds line with marking
run.Background = Brushes.Yellow;
paragraph.Inlines.Add(run);
paragraph.Inlines.Add("\r\n");
selected = run; // ** remember this element
}
iZeile++;
}
FlowDocument document = new FlowDocument(paragraph);
richTextBox1.Document = new FlowDocument();
richTextBox1.Document = document;
Keyboard.Focus(richTextBox1);
DoEvents(); // ** this is required, probably a bug
selected.BringIntoView(); // **
And the helper method, from here:
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Background,
new Action(delegate { }));
}
you should try one of this methods
richTextBox.SelectionStart = richTextBox.Text.Length;
richTextBox.ScrollToCaret();
.
richTextBox.AppendText(text);
richTextBox.ScrollToEnd();
futher informations are here and here
Edit
ok after a bit of digging in the WPF RichTextBox i thing you cloud try richTextBox.ScrollToVerticalOffset(Offset)
to get the Offset maybe you could use this answer
EDIT 2
ok after some more research i found following Link where you can download this working example
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How check if given string is legal (allowed) file name under Windows?
I have searched about, spent some minutes googling, but i cant apply what i have found, to my context..
string appPath = Path.GetDirectoryName(Application.ExecutablePath);
string fname = projectNameBox.Text;
if (projectNameBox.TextLength != 0)
{
File.Create(appPath + "\\projects\\" + fname + ".wtsprn");
So, i am retrieving the projectNameBox.Text and creating a file with the text as filename, but if i include a :, or a \ or a / etc.. it will just crash, which is normal, as those are not allowed for a folder name..How can i check the text, before the file creation, and remove the characters, or even better, do nothing and advise the user that he can not use those characters?
Thanks in advance
string appPath = Path.GetDirectoryName(Application.ExecutablePath);
string fname = projectNameBox.Text;
bool _isValid = true;
foreach (char c in Path.GetInvalidFileNameChars())
{
if (projectNameBox.Text.Contains(c))
{
_isValid = false;
break;
}
}
if (!string.IsNullOrEmpty(projectNameBox.Text) && _isValid)
{
File.Create(appPath + "\\projects\\" + fname + ".wtsprn");
}
else
{
MessageBox.Show("Invalid file name.", "Error");
}
Alternative there is a regex example in the link provided in the first comment.
You can respond to the TextChanged event from your projectNameBox TextBox to intercept changes made to its contents. This means that you can remove all the invalid characters before creating your path later on.
To create the event handler, click on your projectNameBox control in the designer, click the Events icon in the Properties window, then double-click on the TextChanged event in the list that appears below. The following is a brief example of some code that strips out invalid characters:
private void projectNameBox_TextChanged(object sender, EventArgs e)
{
TextBox textbox = sender as TextBox;
string invalid = new string(System.IO.Path.GetInvalidFileNameChars());
Regex rex = new Regex("[" + Regex.Escape(invalid) + "]");
textbox.Text = rex.Replace(textbox.Text, "");
}
(You'll need a using statement for System.Text.RegularExpressions at the top of your file, too.)
I have implemented arbitrary links in my rtb using the CodeProject found here. The links are not truly links but instead data that is looked up when clicked and returned expanded information on the item clicked.
This all works great. The problem is when I try to save the data off to a database using the RichTextBox1.Rtf method the links are lost. I end up with the value of the text but there is no link data saved off in the Rtf. Is there no Rtf code for hyperlinks? Is there no way around this issue?
I am considering adjusting my approach to something more in line with this issue but I don't want to change everything around if I can find a way to save my custom hyperlinks.
Any suggestions would be great!
---------------UPDATE----------------
Before submitting I did a bit more digging and dug up this blog article which discusses that RTB do not save hyperlinks so I guess I am SOL. The only way around this is by saving off the text in hidden textbox and save that version to the db but that way gets clunky. I think I will go with the second option I found and I thought I would post this anyway since data in StackOverflow seems slim on this topic. Now I know why.
Since this is an old thread, I'll post this just for reference:
Here's a (somewhat) recent solution found in the comments of the same article on CodeProject:
Code:
/// <summary>
/// This additional code block checks the locations of links
/// and desc. it via a string which contains informations of how many links are there
/// .Split('&')-1 and the select information .Select(.Split('&')[i].Split('-')[0],.Split('&')[i].Split('-')[1])
/// After we select the links we can SetSelectionLink(true) to get our links back.
/// </summary>
public string getLinkPositions()
{
string pos = "";
for (int i = 0; i < this.TextLength; i++)
{
this.Select(i, 1);
int isLink = GetSelectionLink();
if (isLink == 1)
{
//the selected first character is a part of link, now find its last character
for (int j = i + 1; j <= this.TextLength; j++)
{
this.Select(j, 1);
isLink = GetSelectionLink();
if (isLink != 1 || j == this.TextLength)
{
//we found the last character's +1 so end char is (j-1), start char is (i)
pos += (i) + "-" + ((j - 1) - (i - 1)) + "&"; //j-1 to i but i inserted -1 one more so we can determine the right pos
i = j; //cont. from j+1
break; //exit second for cont. from i = j+1 (i will increase on new i value)
}
}
}
}
this.DeselectAll();
return pos;
}
/// <summary>
/// This method generates the links back only created via InsertLink(string text)
/// and overloaded InsertLink(string text,int position)
/// </summary>
/// <param name="pos">the pos string from getLinkPositions</param>
public void setLinkPositions(string pos)
{
string[] positions = pos.Split('&');
for (int i = 0; i < positions.Length - 1; i++)
{
string[] xy = positions[i].Split('-');
this.Select(Int32.Parse(xy[0]), Int32.Parse(xy[1]));
this.SetSelectionLink(true);
this.Select(Int32.Parse(xy[0]) + Int32.Parse(xy[1]), 0);
}
this.DeselectAll();
}
How to use the code [sic]:
when you are going to save the rtf, save the return string of getLinkPositions() to, when you want to load the rtf, just load it how you do, and then use the return string from 1st method to get the links bak
Ex :
Save:
some save var = richtext.rtf
additional save value = richtext.getLinkPositions();
Load back
richtext.rtf = some stream gets rtf
richtext.setLinkPositions(additional saved value from
some stream)
To summarize, Rich Text Boxes do not save hyperlinks in the .Rtf field (nor text). The value of the display is saved but not the actual link. Seems like a poor limitation to RTB's IMHO.
There are ways around this case, create custom links like this fellow did or re-evaluate your data on load searching for the keywords (the route I took since the data will never get too large to cause freezing).
The code I used to perform this is as follows and called on load:
foreach (ListViewItem keyword in Keywords.Items)
{
System.Text.RegularExpressions.Regex oKeyword = new System.Text.RegularExpressions.Regex(#"\b" + keyword.Text + #"\b");
foreach (System.Text.RegularExpressions.Match match in oKeyword.Matches(rtb.Text))
{
int index = match.Index;
int length = match.Length;
rtb.Select(index, length);
//This next bit is made available through the use of http://www.codeproject.com/Articles/9196/Links-with-arbitrary-text-in-a-RichTextBox
rtb.InsertLink(match.Value);
}
}
Well, another problem is, that the hyperlink is 'saved' but the click event and the target gets lost...
Only the format and behaviour (cursor changes) is restored.
And if You operate on this restored chunk of text it all gets messed up.
So before doin any 'restore' operations, You need to clean out the <hyperlink> stuff.
Prajakta Joshi made an example for autodetcting hyperlinks - it also contains a cleanup routine:
http://blogs.msdn.com/b/prajakta/archive/2006/10/17/autp-detecting-hyperlinks-in-richtextbox-part-i.aspx
Cheers, Stephan
As the Hyperlink tags don't get lost with saving, another approch would be to scan the loaded document for these tags and reapply it's properties - the click event and the navigate uri.
void restoreHyperlinks()
{
TextRange tr = new TextRange(_richTextBox.Document.ContentStart, _richTextBox.Document.ContentEnd);
TextPointer tp = tr.Start;
bool bFound = false;
foreach (System.Text.RegularExpressions.Match match in UrlRegex.Matches(tr.Text))
{
if (tp == null)
tp = tr.Start;
bFound = false;
while (tp != null && !bFound)
{
if (tp.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
string textRun = tp.GetTextInRun(LogicalDirection.Forward);
int indexInRun = textRun.IndexOf(match.Value);
if (indexInRun > -1)
{
bFound = true;
Inline parent = tp.Parent as Inline;
while (parent != null && !(parent is Hyperlink))
{
parent = parent.Parent as Inline;
}
if (parent is Hyperlink)
{
Hyperlink hyperlink = (Hyperlink)parent;
if (isHyperlink(match.Value))
{
Uri uri = new Uri(match.Value, UriKind.RelativeOrAbsolute);
if (!uri.IsAbsoluteUri)
{
uri = new Uri(#"http://" + match.Value, UriKind.Absolute);
}
if (uri != null)
{
hyperlink.NavigateUri = uri;
hyperlink.Click += Hyperlink_Click;
}
}
}
}
}
tp = tp.GetNextContextPosition(LogicalDirection.Forward);
}
}
}
The regex is:
private static readonly System.Text.RegularExpressions.Regex UrlRegex = new System.Text.RegularExpressions.Regex(#"(?#Protocol)(?:(?:ht|f)tp(?:s?)\:\/\/|~/|/)?(?#Username:Password)(?:\w+:\w+#)?(?#Subdomains)(?:(?:[-\w]+\.)+(?#TopLevel Domains)(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2}))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?");
isHyperlink is another method for checking the URL - the code is taken from:
http://marcangers.com/detect-urls-add-hyperlinks-wpf-richtextbox-automatically/
Hope this helps!
Cheers, Stephan