Changing MultiAutoCompleteTextView Adapter Mid-phrase - c#

When the user is typing something into my MultiAutoCompleteTextView, I re-check the list of available phrases after each space. Some of my suggested values contain spaces, and I expect that the user will be able to type a space and have the suggested values continue to list only strings that begin with the entered word, so long as my ITokenizer's FindTokenStart function returns the position representing the beginning of the word before the space. This is actually working in cases where I don't change the Adapter to a different value when typing space. But there's a problem if it changes when typing space.
For example, I have the phrase "Lunch Token" in my suggested value list, and as I begin to type it, everything looks fine:
It continues to look good up through the end of the word "Lunch". Then I add a space, and I have some logic that detects that because the full word lunch has been entered, a couple of the options are no longer valid. So it switches the Adapter property of the MultiAutoCompleteTextView to a different adapter that has fewer words in it. But doing this while the list is popped up appears to cause the list to get populated with all the possible values:
I have added code in my FindTokenStart function to verify that is is returning the right values, and it is. So it must have something to do with changing the adapter while the list is popped up. How do I work around this and force the list to continue to only display matching terms? I tried only updating the Adapter when the new list was not a subset of the prior list, but this causes other problems (I can't remember them at the moment).
Highest level code that changes the Adapter property:
private void Command_AfterTextChanged(object sender, Android.Text.AfterTextChangedEventArgs e)
{
try
{
if (!(command.Text.EndsWith(" ") || command.Text.Length == 0))
return;
var words = new SuggestionCache.WordList();
if (command.Text.Length > 0)
words.AddFromParserStates(parser.ParsePartial(command.Text, currentStory));
else
words.AddFromSyntaxes(Syntax.currentSyntaxes);
command.Adapter = suggestions.GetArrayAdapter(words);
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
}
FindTokenStart implementation:
public int FindTokenStart(ICharSequence text, int cursor)
{
System.Diagnostics.Debug.Write("FindTokenStart: ");
if ((currentWordList == null) || (currentWordList.tokenBounds.Count == 0))
{
System.Diagnostics.Debug.WriteLine("A 0");
return 0;
}
int idx = currentWordList.tokenBounds.BinarySearch(new Syntax.SyntaxMatch() { startPos = cursor }, SyntaxMatchComparer.EndComparer);
if (idx >= 0)
{
System.Diagnostics.Debug.WriteLine("B {0}", currentWordList.tokenBounds[idx].startPos);
return currentWordList.tokenBounds[idx].startPos;
}
if (~idx < currentWordList.tokenBounds.Count)
{
System.Diagnostics.Debug.WriteLine("C {0}", currentWordList.tokenBounds[~idx].startPos);
return currentWordList.tokenBounds[~idx].startPos;
}
var lastBound = currentWordList.tokenBounds.LastOrDefault((b) => b.matched == 2);
if (lastBound.NextPos <= cursor)
{
System.Diagnostics.Debug.WriteLine("D {0}", lastBound.NextPos);
return lastBound.NextPos;
}
else
{
System.Diagnostics.Debug.WriteLine("E {0}", lastBound.startPos);
return lastBound.startPos;
}
}
Debug output while pressing the space key:
[0:] FindTokenStart:
[0:] D 5
[0:] FindTokenStart:
[0:] D 5
[0:] FindTokenStart:
[0:] D 5
[0:] FindTokenStart:
[0:] B 5
01-11 19:08:10.743 E/Surface ( 3326): getSlotFromBufferLocked: unknown buffer: 0x9a5e7d60
01-11 19:08:10.759 E/EGL_emulation( 3326): tid 3369: eglSurfaceAttrib(1210): error 0x3009 (EGL_BAD_MATCH)
01-11 19:08:10.759 W/OpenGLRenderer( 3326): Failed to set EGL_SWAP_BEHAVIOR on surface 0x98422b20, error=EGL_BAD_MATCH
I'm using Visual Studio 2017 and Xamarin top develop this Android application, and testing it on the Android emulator if it matters.

It shouldn't be necessary to change the adapter for a MultiAutoCompleteTextView mid-phrase. The expectation is that all the options are given to the adapter, and the adapter filters that list as more characters are provided, determining which options are still relevant within the current token/phrase. The reason I was changing adapters so often was out of laziness about determining the appropriate times to determine what new adapter should be applied. It shouldn't be based only on a space separator if space is not always the terminator of a token.
My solution was to make the code smarter in trying to ensure that the adapter is only updated at the separation between two tokens. This was relatively easy to accomplish because my existing parser knew where the last token ended and where the next should begin. When these were the same, it means that the token was still incomplete. Once a complete token value was entered and terminated by whitespace, the end of the last token would be less than the beginning of the next token. So by adding this condition before updating the adapter, my problem is resolved:
if (words.tokenBounds.Count == 0 || words.tokenBounds.Last().NextPos > words.tokenBounds.Last().EndPos)
command.Adapter = suggestions.GetArrayAdapter(words);

Related

Dealing with duplicate characters

I'm making a wild west duelling game based on typing of the dead. You have a word to write in a certain amount of time. You win if you type out the word in time, you lose if you type it incorrectly/press the wrong button or if the time runs out.
Currently I've got everything working fine. A slight issue, however, is with how I'm dealing with displaying the letters you have to type on the screen.
Each character is stored into an array that is looped through and displayed on the screen. When the player presses the correct button, the corresponding display should turn red which it does most of the time. The times where it doesn't is when there are duplicate characters.
For example if I was typing the word 'dentist', when I type the first t, it won't turn red. However, when I get to the second t and press it, both turn red. I assume this is because I'm looping through each displayed character and checking to see if it's relevant input is being pressed and because there's two and I can only type one character at a time one is always false which 'overrides' the one that is true. I'm not sure how to implement a solution with how I'm currently dealing input so any help is appreciated!
Code:
if (Duelling)
{
if (currentWord.Count > 0 && Input.inputString == currentWord[0].ToLower())
{
print(Input.inputString);
string pressedKey = currentWord[0];
currentWord.Remove(currentWord[0]);
}
else if (Input.inputString != "" && Input.inputString != currentWord[0].ToLower())
{
DuelLost();
}
if (currentWord.Count <= 0)
{
DuelWon();
}
foreach(Transform Keypad in keyDisplay.transform)
{
//print(Keypad.Find("KeyText").GetComponent<Text>().text);
Keypad.Find("KeyText").GetComponent<Text>().color = currentWord.Contains(Keypad.Find("KeyText").GetComponent<Text>().text) ? Color.black : Color.red;
}
}
I believe the issue lies in your colour-updating logic. Contains naturally returns true if your array, well, contains the text you're looking for. Since the second T in "dentist" is still present in the array after you type the first one in, the component isn't going to change its colour. When inputting the second T, all instances of Ts are cleared from the list, and since you loop over all of your Text components all the time, both of them will become red.
No offence, but you're going about this rather... crudely. Allow me to suggest a more elegant method:
public String currentWord;
private List<Text> letterViews = new List<Text>();
private int curIndex = 0;
void Start() {
// Populate the list of views ONCE, don't look for them every single time
letterViews = ... // How you do this is entirely up to you
}
void Update() {
// ...
if (Duelling) {
// If we've gone through the whole word, we're good
if (curIndex >= currentWord.Length) DuelWon();
// Now check input:
// Note that inputString, which I've never used before, is NOT a single character, but
// you're using only its first character; I'll do the same, as your solution seems to work.
if (Input.inputString[0] == currentWord[currentIndex]) {
// If the correct character was typed, make the label red and increment index
letterViews[currentIndex].color = Color.red;
currentIndex++;
}
else DuelLost();
}
}
I daresay that this is a much simpler solution. DuelWon and DuelLost shall reset the index to 0, clear the text in all letterViews and turn them back to black, perhaps.
How to populate the list of views: you can make it public and manually link them by hand through the inspector (boring), or you can do it iteratively using Transform.GetChild(index). You've probably got enough Text views to accommodate your longest words; I recommend filling the list up with them all. You only do it once, you lose no performance by doing so, and you can re-use them for any words in your dictionary.

How to get most recent database entry and compare it to a textfield

having an ongoing problem with my project that I'm working on. Fairly new to C# and ASP.Net.
I'm currently trying to get an entry from a textfield and compare it to the last entry in my database. For my business rule, the Reading must not be higher than the Previous Years reading. I will have multiple Readings from different machines.
meterReading is the instance of my class MeterReading
This is currently what I have:
var checkMeterReading = (from p in db.MeterReading
where (p.Reading < meterReading.Reading)
select p);
if (checkMeterReading.Count() > 0)
{
if (!String.IsNullOrEmpty())
{
//saves into DB
}
}
else
{
TempData["Error"] = "Meter Reading must be higher than last actual";
}
Don't know if I'm doing anything stupid or not. Thanks in advance
You're currently checking whether any reading in the database is less than the current reading; that's clearly not right, as you could have stored readings of 200, 5000, 12005 and be testing against 9000. There are 2 readings less than 9000, so your code would allow you to insert the 9000 at the end. What you want to check is that all the readings are less, or equivalently: that no reading is higher:
var higherExists = db.MeterReading.Any(p => p.Reading > newReading);
if(higherExists) {
// danger!
} else {
// do the insert... as long as you're dealing with race conditions :)
}
Note that a better approach IMO would be to compare using time, since errors and meter replacements mean that the readings are not necessarily monotonic. Then you'd do something like:
var lastRow = db.MeterReading.OrderByDescending(p => p.ReadingDate).FirstOrDefault();
if(lastRow == null || lastRow.Reading < newReading) {
// fine
} else {
// danger
}
Note that your current code only supports one customer and meter. You probably also need to filter the table by those.

Out of bounds exception whilst using arrays

I have an array of textboxes in which they change dyanmically depending on what the user types in. Those textboxes contain a number which represents a score of an assignment. Those score are linked to a module object. So if the user has 3 modules; 2 assignments on the first and second module and 3 assignments on the third module; then in total there would be 7 textboxes created for the user to input all their assignment marks.
What I am trying to do is to create a keyup event handler in which it gets the number in typed in by the user, and then dynamically calls a method to display the average of the the module. This is what I have so far. The following method gets called whenever the user types in a character:
public void calculateLevel4Modules(int counter) {
//iterate through modules
//iterate through assignts in that module
//whilst iterating, check tb and set userscore
//after iterating, update overall label with regards to modulecounter
//int assignmentCounter = 0;
//Console.WriteLine("in If statement.. " + counter);
for (int moduleCounter = 0; moduleCounter < requiredLevelList().Count; moduleCounter++)
{
int totalNumberOfAssignmentsInCurrentModule = requiredLevelList().ElementAt(moduleCounter).Assignments.Count;
Console.WriteLine("total number of assignmetns: " + totalNumberOfAssignmentsInCurrentModule);
assignmentCounter = assignmentCounter + totalNumberOfAssignmentsInCurrentModule;
Console.WriteLine("assignment counter: " + totalNumberOfAssignmentsInCurrentModule);
if (counter < assignmentCounter)
{
Console.WriteLine("in If statement.. " + userMarksTBLvl4[moduleCounter].Text);
try
{
int userMark = int.Parse(userMarksTBLvl4[counter].Text);
requiredLevelList().ElementAt(moduleCounter).Assignments.ElementAt(counter).UsersScore = userMark;
double modAvg = requiredLevelList().ElementAt(moduleCounter).getModuleScoreOverall();
moduleOverallLvl4[moduleCounter].Text = modAvg.ToString();
break;
}
catch (FormatException) { break; }
}
else { }
}
it works fine if the user has one module but if the user has two or more, then I get an error in the following line:
requiredLevelList().ElementAt(moduleCounter).Assignments.ElementAt(counter).UsersScore = userMark;
I am getting an out of bounds exception. I know why; its because counter is basically the # of the textbox that was typed into but by me using counter, I am accessing something not within the assignments list. This is an example of when the problem occus:
The user has 2 modules. In each module there are 2 assignments thus 4 textboxes are been created with their index ranging from 0 - 3. If the user wants to type in their score of the first assignment on the second module, its basically trying to write to the third index in that element then it crashes since that module only consist of 2 assignments.
There are some strange things in your code that make it hard to answer. First, the code you posted doesn't compile, so we have no way to test it.
Several times you use code like:
requiredLevelList().ElementAt(moduleCounter)
I assume requiredLevelList is a method that returns a list of things. There is no reason to assume requiredLevelList returns the same list, or even lists with the same number of elements, each time you call it. Maybe it does in your particular case, but this is a dangerous thing to rely on. You should use a construct like:
foreach (var module in requiredLevelList())
{
int totalNumberOfAssignmentsInCurrentModule = module.Assignments.Count;
...
module.Assignments.ElementAt(counter).UsersScore = userMark;
...
}
Code like this:
Console.WriteLine("total number of assignmetns: " + totalNumberOfAssignmentsInCurrentModule);
is symptomatic of trying to debug something after it has crashed. That is extremely inefficient. Learn how to use a debugger; you will not become an effective programmer until you know how to do this.
requiredLevelList().ElementAt(moduleCounter).Assignments.ElementAt(counter).UsersScore = userMark;
You're probably getting an out-of-bounds exception here because counter is outside the indexes of Assignments. Since you never initialize or change counter, I have no way to know what it is or should be. A debugger will tell you this, use one.
the # of the textbox that was typed into but by me using counter, I am accessing something not within the assignments list.
OK, if you're typing something “not within the assignments list” then you have to test for that and decide what to do. Perhaps something like:
if (counter >= 0 && counter < module.Assignments.Count)
module.Assignments.ElementAt(counter).UsersScore = userMark;
else
throw new Exception("I really have no idea what you want to do here.");
This also looks wrong:
moduleOverallLvl4[moduleCounter].Text = modAvg.ToString();
You never tell us what moduleOverallLvl4 is, but here you're assuming it has the same size as what is returned by requiredLevelList(). Maybe they are in this particular case, but that is a dangerous assumption. If these values are related, moduleOverallLvl4 should be contained in whatever class implements requiredLevelList, and you should have a method that assigns getModuleScoreOverall() to the correct element of moduleOverallLvl4.

Undo Operation fails on Repeated Use

I created an undo operation to set the current Value to the previous one that was within a list. It accomplishes this by removing the most recent index from the list and setting the value to the value that is behind it. Currently it works when there no value with in the list at initialization, as well as if there is more than one value within the list.
The code the way it is works when there is at least one index in the array, as previous unit tests pass this.
The problem is occurring where I try to have the InvalidOperationException to pass when the command is run twice, after the last item in the list has also been removed. so Add(5) -> Undo() -> Undo() to give an example. Also changing the if statement from valDict.Count > 0 to Value > 0, the same error occurs.
List<int> valDict = new List<int>();
public void Undo()
{
repOp1 = "Undo";
Console.WriteLine("1: " + valDict.Count);
if ( valDict.Count > 0)
{
int temp = valDict.Count - 1;
Console.WriteLine("2: " + temp);
valDict.RemoveAt(temp);
Console.WriteLine("3: " + valDict.Count);
valDict.TrimExcess();
//Below Line is flagged as the error
Value = valDict[valDict.Count-1];
}
else
{
throw new InvalidOperationException();
}
}
Can someone looking at this with fresh eyes, point out a possible solution, but not a fixed implementation, as I need to fix the implementation myself.
You can control that your List is not empty before getting the Value: list.Count==0, or !list.Any().

Recursive woes - reducing an input string

I'm working on a portion of code that is essentially trying to reduce a list of strings down to a single string recursively.
I have an internal database built up of matching string arrays of varying length (say array lengths of 2-4).
An example input string array would be:
{"The", "dog", "ran", "away"}
And for further example, my database could be made up of string arrays in this manner:
(length 2) {{"The", "dog"},{"dog", "ran"}, {"ran", "away"}}
(length 3) {{"The", "dog", "ran"}.... and so on
So, what I am attempting to do is recursively reduce my input string array down to a single token. So ideally it would parse something like this:
1) {"The", "dog", "ran", "away"}
Say that (seq1) = {"The", "dog"} and (seq2) = {"ran", "away"}
2) { (seq1), "ran", "away"}
3) { (seq1), (seq2)}
In my sequence database I know that, for instance, seq3 = {(seq1), (seq2)}
4) { (seq3) }
So, when it is down to a single token, I'm happy and the function would end.
Here is an outline of my current program logic:
public void Tokenize(Arraylist<T> string_array, int current_size)
{
// retrieve all known sequences of length [current_size] (from global list array)
loc_sequences_by_length = sequences_by_length[current_size-min_size]; // sequences of length 2 are stored in position 0 and so on
// escape cases
if (string_array.Count == 1)
{
// finished successfully
return;
}
else if (string_array.Count < current_size)
{
// checking sequences of greater length than input string, bail
return;
}
else
{
// split input string into chunks of size [current_size] and compare to local database
// of known sequences
// (splitting code works fine)
foreach (comparison)
{
if (match_found)
{
// update input string and recall function to find other matches
string_array[found_array_position] = new_sequence;
string_array.Removerange[found_array_position+1, new_sequence.Length-1];
Tokenize(string_array, current_size)
}
}
}
// ran through unsuccessfully, increment length and try again for new sequence group
current_size++;
if (current_size > MAX_SIZE)
return;
else
Tokenize(string_array, current_size);
}
I thought it was straightforward enough, but have been getting some strange results.
Generally it appears to work, but upon further review of my output data I'm seeing some issues. Mainly, it appears to work up to a certain point...and at that point my 'curr_size' counter resets to the minimum value.
So it is called with a size of 2, then 3, then 4, then resets to 2.
My assumption was that it would run up to my predetermined max size, and then bail completely.
I tried to simplify my code as much as possible, so there are probably some simple syntax errors in transcribing. If there is any other detail that may help an eagle-eyed SO user, please let me know and I'll edit.
Thanks in advance
One bug is:
string_array[found_array_position] = new_sequence;
I don't know where this is defined, and as far as I can tell if it was defined, it is never changed.
In your if statement, when if match_found ever set to true?
Also, it appears you have an extra close brace here, but you may want the last block of code to be outside of the function:
}
}
}
It would help if you cleaned up the code, to make it easier to read. Once we get past the syntactic errors it will be easier to see what is going on, I think.
Not sure what all the issues are, but the first thing I'd do is have your "catch-all" exit block right at the beginning of your method.
public void Tokenize(Arraylist<T> string_array, int current_size)
{
if (current_size > MAX_SIZE)
return;
// Guts go here
Tokenize(string_array, ++current_size);
}
A couple things:
Your tokens are not clearly separated from your input string values. This makes it more difficult to handle, and to see what's going on.
It looks like you're writing pseudo-code:
loc_sequences_by_length is not used
found_array_position is not defined
Arraylist should be ArrayList.
etc.
Overall I agree with James' statement:
It would help if you cleaned up the
code, to make it easier to read.
-Doug

Categories

Resources