How to create a crossword puzzle in WPF? - c#

I created 10 x 10 TextBoxes and the user should input the words into the relevant TextBoxes for the position of the words. And I save everything into a textfile like this:
Then on WPF side , I read the textfile and populate the TextBoxes in a panel but the problem is that the crossword puzzle has down and across hints that lead you to the answer and each hint will have a number to indicate which is which. However I can't think of a way to link the number's puzzle number to the hints down and across number. This is how it looks like now:
Notice the numbers (I edited them in paint to visualize what I want) beside the across and down, I need that numbers to be displayed.
In my database, I stored the location of the file in a table, and the hints and answer in another table like this:
And this is the hints (across and down) and answer:
I am using Entity framework lambda expressions to retrieve the across and down.
Appreciate any help on this to link the assign the numbers to Across and Down from the puzzle.
This is my code to display the puzzle :
protected void Across()
{
IList<ModelSQL.puzzlecontent> lstAcross = daoPuzzleContent.GetAcross();
foreach (ModelSQL.puzzlecontent lista in lstAcross)
{
Label tbA = new Label();
tbA.Content = lista.Hint;
tbA.Width = Double.NaN;
tbA.BorderBrush = Brushes.CadetBlue;
tbA.BorderThickness = new Thickness(2);
stackPanel1.Width = Double.NaN;
stackPanel1.Children.Add(tbA);
words.Add(lista.Answer);
}
}
protected void AddPuzzle()
{
// foldername of the txt file.
// using (StreamReader reader = File.OpenText((#daoWordPuzzle.GetfileURL())))
string[] fileData = File.ReadAllLines(#"C:\Users\apr13mpsip\Desktop\OneOrganizer\OneOrganizer\WordPuzzle\educational.txt");
string[] lineValues;
int row = 0;
int col;
int hint = 1;
string[][] rowcol = new string[fileData.Length][];
foreach (string line in fileData)
{
lineValues = line.Split(new string[] { "," }, StringSplitOptions.None);
rowcol[row] = new string[lineValues.Length];
col = 0;
foreach (string value in lineValues)
{
rowcol[row][col] = value;
col++;
}
row++;
}
for (int i = 0; i < rowcol.GetLength(0) ; i++)
{
for (int j = 0; j < rowcol[i].GetLength(0) ; j++)
{
int iadd = i+1 < rowcol.GetLength(0) ? i+1 : 100;
int iminus = i-1 >= 0 ? i-1 : 100;
int jadd = j+1 < rowcol.GetLength(0) ? j+1 : 100;
int jminus = j-1 >= 0 ? j-1 : 100;
var self = rowcol[i][j]; // current value
var top = iminus == 100 ? "" : rowcol[iminus][j];
var bottom = iadd == 100 ? "" : rowcol[iadd][j];
var left = jminus == 100 ? "" : rowcol[i][jminus];
var right = jadd == 100 ? "" : rowcol[i][jadd];
//ACROSS HORIZONTAL
if (
(!String.IsNullOrEmpty(self) && !String.IsNullOrEmpty(right) && !String.IsNullOrEmpty(bottom) && String.IsNullOrEmpty(top) && String.IsNullOrEmpty(left)) ||
(!String.IsNullOrEmpty(self) && !String.IsNullOrEmpty(right) && String.IsNullOrEmpty(bottom) && !String.IsNullOrEmpty(top) && String.IsNullOrEmpty(left)) ||
(!String.IsNullOrEmpty(self) && !String.IsNullOrEmpty(right) && String.IsNullOrEmpty(bottom) && String.IsNullOrEmpty(top) && String.IsNullOrEmpty(left)) ||
(!String.IsNullOrEmpty(self) && !String.IsNullOrEmpty(right) && !String.IsNullOrEmpty(bottom) && !String.IsNullOrEmpty(top) && String.IsNullOrEmpty(left))
)
{
wordAcross = "";
for (int k = 0; k < 10; k++)
{
wordAcross += rowcol[i][k];
if (k == 9)
{
puzzlewordAcross.Add(wordAcross);
// print hello and live
}
}
}
//DOWN VERTICAL
if (
(!String.IsNullOrEmpty(self) && String.IsNullOrEmpty(right) && !String.IsNullOrEmpty(bottom) && String.IsNullOrEmpty(top) && !String.IsNullOrEmpty(left)) ||
(!String.IsNullOrEmpty(self) && String.IsNullOrEmpty(right) && !String.IsNullOrEmpty(bottom) && String.IsNullOrEmpty(top) && String.IsNullOrEmpty(left)) ||
(!String.IsNullOrEmpty(self) && !String.IsNullOrEmpty(right) && !String.IsNullOrEmpty(bottom) && String.IsNullOrEmpty(top) && String.IsNullOrEmpty(left))
)
{
wordDown = "";
for (int k = 0; k < 10; k++)
{
wordDown += rowcol[k][j];
if (k == 9)
{
puzzlewordDown.Add(wordDown);
// print holy and leducated
}
}
}
//Check Top , Left , Bottom , Right value.
if (
(!String.IsNullOrEmpty(self) && String.IsNullOrEmpty(top) && !String.IsNullOrEmpty(right) && !String.IsNullOrEmpty(bottom) && String.IsNullOrEmpty(left)) ||
(!String.IsNullOrEmpty(self) && String.IsNullOrEmpty(top) && String.IsNullOrEmpty(right) && !String.IsNullOrEmpty(bottom) && !String.IsNullOrEmpty(left)) ||
(!String.IsNullOrEmpty(self) && !String.IsNullOrEmpty(top) && !String.IsNullOrEmpty(right) && String.IsNullOrEmpty(bottom) && String.IsNullOrEmpty(left)) ||
(!String.IsNullOrEmpty(self) && !String.IsNullOrEmpty(top) && !String.IsNullOrEmpty(right) && !String.IsNullOrEmpty(bottom) && String.IsNullOrEmpty(left)) ||
(!String.IsNullOrEmpty(self) && String.IsNullOrEmpty(top) && String.IsNullOrEmpty(right) && !String.IsNullOrEmpty(bottom) && String.IsNullOrEmpty(left)) ||
(!String.IsNullOrEmpty(self) && String.IsNullOrEmpty(top) && !String.IsNullOrEmpty(right) && String.IsNullOrEmpty(bottom) && String.IsNullOrEmpty(left))
)
{
TextBox tbox = new TextBox();
tbox.Height = 50;
tbox.Width = 50;
tbox.Text = hint.ToString();
wrapPanel1.Children.Add(tbox);
tbox.GotFocus += (source, e) =>
{
if (!string.IsNullOrEmpty(tbox.Text))
{
string Str = tbox.Text.Trim();
double Num;
bool isNum = double.TryParse(Str, out Num);
if (isNum)
tbox.Text = "";
}
else
{
tbox.Text = "";
}
};
hint++;
}
else
{
TextBox tbox2 = new TextBox();
tbox2.Height = 50;
tbox2.Width = 50;
if (String.IsNullOrEmpty(self))
{
tbox2.Background = Brushes.Black;
tbox2.Focusable = false;
}
wrapPanel1.Children.Add(tbox2);
}// end of top bottom left right.
}
}
} // End of AddPuzzle()
Code to display Across and Down :
protected void Down()
{
IList<ModelSQL.puzzlecontent> lstDown = daoPuzzleContent.GetDown();
foreach (ModelSQL.puzzlecontent listd in lstDown)
{
Label tbD = new Label();
tbD.Content = listd.Hint;
tbD.Width = Double.NaN;
tbD.BorderBrush = Brushes.CadetBlue;
tbD.BorderThickness = new Thickness(2);
stackPanel2.Width = Double.NaN;
stackPanel2.Children.Add(tbD);
}
}
protected void Across()
{
IList<ModelSQL.puzzlecontent> lstAcross = daoPuzzleContent.GetAcross();
foreach (ModelSQL.puzzlecontent lista in lstAcross)
{
Label tbA = new Label();
tbA.Content = lista.Hint;
tbA.Width = Double.NaN;
tbA.BorderBrush = Brushes.CadetBlue;
tbA.BorderThickness = new Thickness(2);
stackPanel1.Width = Double.NaN;
stackPanel1.Children.Add(tbA);
words.Add(lista.Answer);
}
}

You may need to re-define your data model here because I think you have failed at the first hurdle - systems analysis. It catches us all out when we just want to write code and always means a big refactor.
Think about your domain here. Crossword puzzles. If we are a reading a newspaper puzzle and you dont know the answer to a clue, what do you say when you you ask a friend.
6 letters, clue is 'blah blah'
...followed by any letters that you already know.
Now we know that the puzzle needs to know how many letters in each answer and that each answer needs a clue. We also know that letters are hidden until you fill them out but we need to know the right answer at some point.
How does the puzzle present itself in the back of the paper? Clues are not written as 1,2,3 etc. They are 4 down, 1 across etc. Now we know that you need some position data stored.
You can achieve this is 2 ways.
1. Have each clue as its own entry complete with text
Clue '1'
Direction 'Across'
Position '1,1'
Answer 'Hello'
Description 'Greeting'
Work out the grid size from the entries and position letters accordingly.
Pros: Easy to work with. All the information in one place
Cons: Possible data corruption. This method can define 2 different letters in the same position.
2. Seperate Entries but answer text in grid
This is very similar to how you have it now but your seperate the text into a CSV grid as you demonstrate in the first screenshot. You then have entries for the clues as in method 1. but omit the 'Answer' field.
Your code will have to :
work out the grid size
populate a grid
populate the list of
clues
convert the users entries to a CSV text file so that you
can validate the input against the answers and
tell the user if
As for linking the clues to the entry text boxes. Set the Tooltip property of each textbox with descriptions of the clues that include the letter.
they got it right.
Finally (and this is probably the bit you want), add the correct number to the entry text box, you have to take advantage on WPF's layout pipeline. Dont just put a textbox in your grid, put another grid in! I'll show you how it should look in XAML, but you may want to generate it in code.
<Grid>
<TextBlock x:Name="TextBlock_NumberLabel"/>
<TextBox x:Name="TextBox_LetterEntry"/>
<Grid>
Use that instead of a plain textbox in any square where you want a number.

Related

Input only digits and control buttons

I want to input Salary with any value: 550,49, 2222,12, 9,3 and so on. But need to use control button like this: ,, backspace, ctrl + c, ctrl + v, ctrl + a.
Salary is TextBox with ShortcutsEnabled = true and event:
private void TbSalary_KeyPress(object sender, KeyPressEventArgs e)
{
char number = e.KeyChar;
if ((e.KeyChar <= 47 || e.KeyChar >= 58) && number != 8 && number != 44)
//digits, BackSpace and ,
{
e.Handled = true;
}
}
If remove this condition, the specified combinations will work. But not only numbers are entered.
Should I add tracking of all combinations here? Or is it possible to implement this task in another way?
MaskedTextBox requires a fixed number of characters with some "mask". But the Salary is different. Can be **,**, ******,* or *** and etc.
UPDATE
Prevent entering more than two numbers after the decimal point
if (number < ' ')
{
return;
}
if (number >= '0' && number <= '9')
{
if (this.Text.Contains(',')
&& this.SelectionLength == 0
&& this.SelectionStart > this.Text.IndexOf(',')
&& this.Text.Length - this.Text.IndexOf(',') > 2)
{
e.Handled = true;
}
return;
}
Please, don't use magic numbers like 47, let's work with characters. We should allow these characters:
'0'..'9' range (numbers)
control characters (which are below space ' ') for tab, backspace etc.
',' (comma) as a decimal separator
All the other characters should be banned.
Code:
private void TbSalary_KeyPress(object sender, KeyPressEventArgs e)
{
char number = e.KeyChar;
TextBox box = sender as TextBox;
if (number >= '0' && number <= '9' || number < ' ')
return; // numbers as well as backspaces, tabs: business as usual
else if (number == ',') {
// We don't want to allow several commas, right?
int p = box.Text.IndexOf(',');
// So if we have a comma already...
if (p >= 0) {
// ... we don't add another one
e.Handled = true;
// but place caret after the comma position
box.SelectionStart = p + 1;
box.SelectionLength = 0;
}
else if (box.SelectionStart == 0) {
// if we don't have comma and we try to add comma at the 1st position
e.Handled = true;
// let's add it as "0,"
box.Text = "0," + box.Text.Substring(box.SelectionLength);
box.SelectionStart = 2;
}
}
else
e.Handled = true; // all the other characters (like '+', 'p') are banned
}
Please, note, that there is possibility to Paste incorrect value (say, "bla-bla-bla") into TbSalary TextBox; to prevent it you can use TextChanged event:
private void TbSalary_TextChanged(object sender, EventArgs e) {
TextBox box = sender as TextBox;
StringBuilder sb = new StringBuilder();
bool hasComma = false;
foreach (var c in box.Text)
if (c >= '0' && c <= '9')
sb.Append(c);
else if (c == ',' && !hasComma) {
hasComma = true;
if (sb.Length <= 0) // we don't start from comma
sb.Append('0');
sb.Append(c);
}
string text = sb.ToString();
if (!text.Equals(box.Text))
box.Text = text;
}

Pasting data from clipboard into Datagrid is extremely slow

I'm implementing a custom control based on the WPF DataGrid.
One of the things I implemented is pasting from clipboard.
For some reason the approach I'm taking is performing really slow.
I did some performance evaluation and it seems that BeginEdit() is taking around 80% of exclusive samples.
Can anyone provide any insights on what I maybe doing wrong or have a different approach to pasting data into WPF DatagGrid?
Here's my method doing the work:
private void OnExecutedPaste(object sender, ExecutedRoutedEventArgs e)
{
// get clipboard content
List<object[]> rowData = ClipboardHelper.ParseClipboardDataToTypes();
var selectedCellContent = SelectedCells[0].Column.GetCellContent(SelectedCells[0].Item);
if (selectedCellContent != null)
{
var firstCell = SelectedCells.Count > 0 ? selectedCellContent.Parent as DataGridCell : null;
// Get the start & end rows/columns indexes
int minRowIndex = firstCell != null ? firstCell.GetParentRow().GetIndex() : Items.Count - 1;
int maxRowIndex = minRowIndex + rowData.Count;
int minColumnDisplayIndex = (SelectionUnit != DataGridSelectionUnit.FullRow) && firstCell != null
? firstCell.Column.DisplayIndex
: 0;
int maxColumnDisplayIndex = Columns.Count - 1;
// Go through rows
int rowDataIndex = 0;
for (int i = minRowIndex; i <= maxRowIndex && rowDataIndex < rowData.Count; i++, rowDataIndex++)
{
int columnDataIndex = 0;
// Get row view model bound to the row
var rowVM = Items[i];
// Go through columns
for (int j = minColumnDisplayIndex;
j <= maxColumnDisplayIndex && columnDataIndex < rowData[rowDataIndex].Length;
j++, columnDataIndex++)
{
// Get the column
var column = ColumnFromDisplayIndex(j);
// Get the value to be pasted at the cell
object value = rowData[rowDataIndex][columnDataIndex];
CurrentCell = new DataGridCellInfo(rowVM, column);
BeginEdit();
// If first cell in the row we need to refresh item in case is the newitemplaceholder
// BeginEdit() may have triggered NewItemInitializer
if (j == minColumnDisplayIndex) rowVM = Items[i];
// Paste the value in the cell
column.OnPastingCellClipboardContent(rowVM, value);
//CommitEdit(DataGridEditingUnit.Cell, true);
}
if (!CommitEdit())
{
MessageBox.Show(string.Format("Cannot paste clipboard content at row {0}. Make sure the data is valid.", i), "Can't paste row",
MessageBoxButton.OK, MessageBoxImage.Exclamation);
return;
}
if (i >= Items.Count - 1)
{
if (NewItemInitializer == null)
MessageBox.Show("Cannot add new rows for additional items.", "Can't paste row",
MessageBoxButton.OK, MessageBoxImage.Exclamation);
else
OnInitializingNewItem(new InitializingNewItemEventArgs(NewItemInitializer.Invoke()));
}
}
}
}

How to limit the length of each line in a multiline textbox on PowerPoint TaskPane using C#?

I have created a task-pane in PowerPoint VSTO add-in which I have developed on .Net 4.0.
On the task-pane, I have a text box where the user has to enter only numeric data.
The requirement is as below:
The user can enter more than one numeric data by typing one data on each line.
Each data can contain up to 8 characters, including: numbers, decimals and commas. If a line exceeds 8 characters, it should be truncated to 8 characters.
Below is the code that I am using:
public void splitString(string[] strText)
{
string[] arr = txtEntryField.Lines;
for (int n = 0; n < arr.Length; n++)
{
if (arr[n].Length > 8)
{
arr[n] = arr[n].Substring(0, 8);
}
}
txtEntryField.Lines = arr;
if (txtEntryField.Lines.Length > 0)
{
txtEntryField.SelectionStart = txtEntryField.Text.Length;
}
}
I am calling this method on txtEntryField_TextChanged event. While I am almost there, I think the operation and user experience is not so smooth.
Updated the code so that user is not able to enter characters in the textbox. This is done by the following code:
void txtEntryField1_KeyPress(object sender, KeyPressEventArgs e)
{
const char Delete = (char)8;
var regex = new Regex(#"[^.,0-9\s]");
if (regex.IsMatch(e.KeyChar.ToString()) && e.KeyChar != Delete && e.KeyChar != (char)Keys.Enter && e.KeyChar != (char)Keys.Back)
{
e.Handled = true;
}
}
Can any one help me with a better solution?
Any help is most welcome.
Thanks.
This worked for me:
public void splitString(string[] strText)
{
string[] arr = txtEntryField.Lines;
for (int n = 0; n < arr.Length; n++)
{
if (arr[n].Length > 8)
{
arr[n] = arr[n].Substring(0, 8);
}
}
txtEntryField.Lines = arr;
if (txtEntryField.Lines.Length > 0)
{
txtEntryField.SelectionStart = txtEntryField.Text.Length;
}
}
Also the below code allows the user only to enter the desired characters:
void txtEntryField1_KeyPress(object sender, KeyPressEventArgs e)
{
const char Delete = (char)8;
var regex = new Regex(#"[^.,0-9\s]");
if (regex.IsMatch(e.KeyChar.ToString()) && e.KeyChar != Delete && e.KeyChar != (char)Keys.Enter && e.KeyChar != (char)Keys.Back)
{
e.Handled = true;
}
}

checking the file sizes not passing test cases

I am comparing two excel files and for my current test - I have two files exactly the same each containing one column and 5 rows. When I check if they are equal or not, it fails and says that one of the files has added items to it.
Not sure if I am checking something the wrong way:
bool areEqual = false;
if (fileB.excelRows.Count() == fileA.excelRows.Count())
{
int i = 0;
while ((i < fileB.excelRows.Count()) && (fileB.excelRows[i] == fileA.excelRows[i]))
{
i += 1;
}
if (i == fileB.excelRows.Count())
{
areEqual = true;
}
}
if (areEqual)
MessageBox.Show("The files are the same");
else
{
....
}
Use the SequenceEqual method on excelRows[i].rowHash.
Instead of:
while ((i < fileB.excelRows.Count()) && (fileB.excelRows[i] == fileA.excelRows[i]))
Change it to:
while ((i < fileB.excelRows.Count()) &&
(fileB.excelRows[i].rowHash.SequenceEqual(fileA.excelRows[i].rowHash)))

List cross-referencing causing lag in c#

I have a game kinda like minecraft, but from a top down perspective, adding blocks is done by when the player left-clicks it adds the block at the specified position, right-clicking on a placed block deletes the block.
The problem I was having was that when the player left-clicks it adds the block and its position to a list (for saving to XML later), so the logical conclusion is that when the player right-clicks it removes said block from the game, and its position from its list.
This is the method that I was using which generated lag:
for (int b = 0; b < game.blocklist.Count; b++)
{
for (int v = 0; v < game.blockpos1.Count; v++)
{
if (game.blocklist[b].visible == true)
{
if (game.cursor.boundingbox.Intersects(game.blocklist[b].blockrectangle) && mousestate.RightButton == ButtonState.Pressed && game.player.Builder == true)
{
if (game.blocklist[b].blockposition.X == game.blockpos1[v].X && game.blocklist[b].blockposition.Y == game.blockpos1[v].Y)
{
game.blockpos1.RemoveAt(v);
game.blocklist.RemoveAt(b);
break;
}
}
}
}
}
Now this is the method that I replaced it with which reduces lag immensely and still achieves the same effect I want:
for (int b = 0; b < game.blocklist.Count; b++)
{
if (game.blocklist[b].visible == true)
{
if (game.cursor.boundingbox.Intersects(game.blocklist[b].blockrectangle) && mousestate.RightButton == ButtonState.Pressed && game.player.Builder == true)
{
if (game.blocklist[b].blockposition.X == game.blockpos1[b].X && game.blocklist[b].blockposition.Y == game.blockpos1[b].Y)
{
game.blockpos1.RemoveAt(b);
game.blocklist.RemoveAt(b);
break;
}
}
}
}
Why does this generate so much lag? I just need clarification as to what I did wrong so I don't do it again.
for (int b = 0; b < game.blocklist.Count; b++)
{
for (int v = 0; v < game.blockpos1.Count; v++)
{
if (game.blocklist[b].visible == true)
{
in this version, basically for every item in blocklist ( X ) whether it was visible or not you are iterating every item in blockpos1 (Y). So you do X * Y loops. You then reduced it to X loops and only evaluated visible items.
Given the way the code is written in the question, I'd check these two things before even trying to loop
mousestate.RightButton == ButtonState.Pressed && game.player.Builder == true
because they will never change during those loops. so if they aren't true to start with, don't even bother looping.
Also as Preseton says in the comments, no need to compare things to true.... you only need to do
mousestate.RightButton == ButtonState.Pressed && game.player.Builder
and
if (game.blocklist[b].visible)

Categories