WPF - MVVM Textbox restrict to specific characters - c#

I am trying to make text box accept only specific characters.
My TextBox is bound to the following:
private string _CompanyID;
public string CompanyID
{
get { return _CompanyID; }
set
{
_CompanyID = UniversalHelpers.sReturnCorrectColumnName(value);
OnPropertyChanged("CompanyID");
}
}
Where this is the function that is being called:
public static string sReturnCorrectColumnName(string sInput)
{
if(!string.IsNullOrWhiteSpace(sInput))
return Regex.Replace(sInput, #"[^a-zA-Z]", string.Empty).ToUpper();
else
return sInput;
}
(I am allowing only a-z & A-Z, nothing else).
Finally my TextBox looks like this:
<TextBox Text="{Binding ExcelBindings.CompanyID, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
What I don't understand is, that user can still write anything he wants, even though my Mode is set to TwoWay.
What am I doing wrong?

You should use a custom UI element there that restricts the input on the view-side using “classic” solutions like change listeners.
For example, you can just create a simple subtype of TextBox that overrides the OnPreviewTextInput method. There, you can decide when some input should go through, or when you want to prevent it.
For example, this is a custom TextBox that takes only characters from the ASCII alphabet:
public class AlphabetTextBox : TextBox
{
private static readonly Regex regex = new Regex("^[a-zA-Z]+$");
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
if (!regex.IsMatch(e.Text))
e.Handled = true;
base.OnPreviewTextInput(e);
}
}
Of course, you could also make the regular expression a property of the text box and allow people to set it from XAML. That way, you would get a very reusable component which you can use for various applications.

I do this with the PreviewtextInput event. I have a generic event used for multiple TextBoxes which takes the regex from a configuration table, but I have hard-coded the regex in this example.
private void GenericTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
e.Handled = !IsTextAllowed(e.Text, #"[^a-zA-Z]");
}
private static bool IsTextAllowed(string Text, string AllowedRegex)
{
try
{
var regex = new Regex(AllowedRegex);
return !regex.IsMatch(Text);
}
catch
{
return true;
}
}

The problem is, humans type in numbers sequentially, the fools.
To type in "0.1", a legitimate string, you have to type in "0.", which fails.
Also, re the accepted answer from #poke (which is great), the e.Text value is the change to the textbox (keystroke).
You must add this change to the current textbox string, and then validate the concatenated candidate string, and see if that is valid.
Humans are also wiley, so they will paste from the clipboard to get around the restriction.
With a textbox, you will never be able to block all garbage in, because at some point the user will have to go through garbage, to get to a valid string.
So you can block illegal character entry using e.Text, or allow sequential step failure. But you will still have to check the final string for validity too.
Below is an example of a textbox that allows users to type in a decimal value with a max of 8 dec places in, but they could still cheat this by pasting from the clipboard.
////////////////////////
// REGEXTEXTBOX CLASS //
////////////////////////
using System.Windows.Controls; // Textbox
using System.Windows.Input;
using System.Text.RegularExpressions; // Regex
namespace MyNamespace
{
public class RegexTextBox : TextBox
{
private Regex _regex = null;
public Regex Regex
{
get { return _regex; }
set { _regex = value; }
}
///////////////////////////////////////////////////////////////////////
// MEMBERS
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
var prefix = "OnPreviewTextInput() - ";
logger.Debug(prefix + "Entering");
string currentText = this.Text;
string candidateText = currentText + e.Text;
// If we have a set regex, and the current text fails,
// mark as handled so the text is not processed.
if (_regex != null && !_regex.IsMatch(candidateText))
{
e.Handled = true;
}
base.OnPreviewTextInput(e);
}
} // end of class RegexTextbox
} // end of MyNamespace
/////////////////////
// MAINWINDOW.XAML //
/////////////////////
//(Window class needs to know your namespace so it needs xmlns:myNamespace="clr-namespace:MyNamespace")
<myNamespace:RegexTextBox
x:Name="textboxPayToAmount"
Text="{Binding PayToAmount}">
</myNamespace:RegexTextBox>
////////////////////////
// MAINWINDOW.XAML.CS //
////////////////////////
namespace MyNamespace
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
textboxPayToAmount.Regex =
new System.Text.RegularExpressions.Regex(#"^\d*(\.\d{0,8})?$");
}
}
}

Public Shared Function GetWordCount(str As String) As Integer
Dim collection As MatchCollection = Regex.Matches(str, "\S+")
Return collection.Count
End Function
Public Shared Function GetInWordLimit(str As String, max_words As Integer) As String
Dim final As String = ""
Dim count As Integer = Core.StringOperations.GetWordCount(str)
Dim avg_max_length As Integer = max_words * 7
Dim words = str.Split(" ")
If (words.Length > max_words - 1 And count > max_words - 1) Then
Dim index As Integer = 0
For Each word In words
If index >= max_words Then Exit For
final &= word & " "
If Not (Char.IsSeparator(word) Or Char.IsWhiteSpace(word) Or word = "") Then
index += 1
End If
Next
final = final.TrimEnd
Else
final = str
End If
If final.Length > avg_max_length - 1 Then final = final.Substring(0, avg_max_length)
Return final
End Function

Related

How to use a string in an if statement with properties Settings?

I am currently working in winforms c#. I have a string "Flippo1SN" and that string is determined in my form. "Flippo1SN" can change in 'serial number names' that matches the same name as in Properties.Settings . The value of the serial numbers in Properties.Settings are integers. I want to use an if statement without summing up all the integers in my Properties.Settings, instead, I want to use a string so I could write my code in 1 if statement instead of more. I tried to use a code like this:
if (Properties.Settings.Default.Flippo1SN == 0) {}
Which, as you may have guessed, did not work. I've tried more ways as:
if (Properties.Settings.Default.(Flippo1SN) == 0) {}
if (Properties.Settings.Default.("Flippo1SN") == 0) {}
if (Properties.Settings.Default.[Flippo1SN] == 0) {}
if (Properties.Settings.Default.["Flippo1SN"] == 0) {}
It gives me an error saying that there is an indentifier expected.
How can I solve this? This question may have already been asked in the past but I couldn't find it.
Thanks in advance.
Edit 1:
Flippo1SN Does not exist in Properties.Settings, it is its value that does. Its value is something like: GF01, GF02, ... I am trying to refer to those.
Edit 2:
These are the variables I am trying to refer to. Flippo1SNs name changes to GF01 or GF02 etc. I don't want to put a code like this:
if (Flippo1SN == "GF01")
{
if (Properties.Settings.Default.GF01 == 0) {//Do action}
}
Instead, I want to refer to GF01 immediately by using Flippo1SN in the second if statement. That would cost me a lot of time and writing because I have a lot of Integers in Properties.Settings .
Edit 3
I'm going to explain what I am creating so you guys understand what I'm doing.
I am creating a 'Collecting Game' where you collect Flippos (Pogs or milk caps is what it's called in English I guess?). To get those Flippos, you open a giftbox and receive 3 random flippos. The output is something like this:
Screenshot ("Verzamel" means collect)
In this image, you see 3 flippos. In the top left corner, you see the Serial Numbers of each flippo (MF06, GF16, OF12). 'MF' stands for 'Mega Flippo', 'GF' for Green Flippo and 'OF' stands for 'Orange Flippo'.
In the code, I have used random to choose which one you get (50% chance for green, 30% chance for orange and 20% chance for mega. The percentage is determined by the amount of a specific group). I have also 3 strings in my code that holds the serial number of these flippos (Flippo1SN, Flippo2SN, and Flippo3SN). These serial numbers refer to the ones in my database (or just the properties.settings tab). In this scenario, MF06, GF16 and OF12 increments by 1.
Now, I want to check if you've received a flippo you didn't have before. If you do so, a label will appear above the picture and the text of that label will be "New".
To do so, I first need to check which one you have received, then check if you already have that in your database. The first if checks if you have received GF01 and the second if checks if you already have it:
if (Flippo1SN == "GF01")
{
if (Properties.Settings.Default.GF01 == 0)
{
label1.Show();
}
}
else if (FLippo1SN == "GF02") {//ETC}
Flippo1SN is already determined. I am not trying to change the Flippo1SNs value. I am just using this string to check which flippo you have received. All flippos have a serial number and Flippo1SN holds a serial number to refer to which flippo you have received.
What I now am asking is, is there a more fast way to do this? Can't I use the value of Flippo1SN immediately in an if statement so I could avoid multiple if statements?
I really hope I made things clear now.
First you have to check if it exists then you can do whatever you want with it:
if(Properties.Settings.Default.ContainsKey(Flippo1SN))
{
if(Properties.Settings.Default[Flippo1SN] == 0)
{
// ....
}
}
You can also write handlers to get rid of the if checks:
interface ISettingsHandler
{
void Handle(int value);
bool CanHandle(string name);
}
class GF1Handler : ISettingsHandler
{
public void Handle(int value){
// do action
}
public bool CanHandle(string propertyName){
return propertyName.Equals("GF1");
}
}
class GF2Handler : ISettingsHandler
{
public void Handle(int value){
// do action
}
public bool CanHandle(string propertyName){
return propertyName.Equals("GF2");
}
}
You can then initialize a list of handlers, and use the one that can handle the selected property:
var handler = listOfHandler.FirstOrDefault(h => h.CanHandle(Flippo1SN))
if( != null)
handler.Handle(Properties.Settings.Default[Flippo1SN]);
By using Properties.Settings.Default you can retrieve all the properties in your Project.
Then, by iterating through them you can check the Name of each property to see if it matches your Flippo1SN like so:
string Flippo1SN = "GF02";
var props = Properties.Settings.Default;
foreach(var prop in props.Properties)
{
var settingProperty = (SettingsProperty)prop;
if (settingProperty.Name == Flippo1SN)
{
// Now you found the property that matches Flippo1SN.
// Get its value.
var value = settingProperty.DefaultValue;
}
}
Edit:
How to check if value of the property is zero:
string Flippo1SN = "GF02";
foreach (SettingsProperty prop in Properties.Settings.Default.Properties)
{
if (prop.Name == Flippo1SN)
{
if (int.TryParse(prop.DefaultValue.ToString(), out int result))
{
if (result == 0)
{
// The value is zero.
}
}
}
}
EDIT: NEW CODE AND SCREENSHOTS
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
dynamic props = Properties.Settings.Default;
string Flippo1SN = "200";
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
string output = "Properties.Settings.Default.test : ";
if (Flippo1SN == props.test.ToString())
{
output += $"{props.test}";
}
if (Flippo1SN == props.test2.ToString())
{
output += $"{props.test2}";
}
if (Flippo1SN == props.test3.ToString())
{
output += $"{props.test3}";
}
MessageBox.Show(output);
}
}
}

Prevent saving an invalid field value in Sitecore

I have a custom validator class, which checks the following:
The value of the text field should have a length 5 characters.
The
first 2 chars. should be numbers.
The last 3 chars. should be
alphabets.
When setting the Standard Value of the template (eg: 12a), the indicator shows Red and the appropriate message. But after pressing Ctrl + S, it shows a dialog asking to save even when there is an error. After clicking OK, there is a similar dialog. Clicking on OK, saves 12a as the standard value for the field. When I refresh the content editor the value is 12a.
Is this normal Sitecore behavior. I'm expecting that the value shouldn't be saved at all, if it is invalid.
namespace CustomValidators
{
[Serializable]
public class testValidator : StandardValidator
{
private readonly Regex numbersRegex = new Regex(#"^\d+$");
private readonly Regex lettersRegexnew = new Regex(#"^[A-Za-z]+$");
protected override ValidatorResult Evaluate()
{
string value = base.GetControlValidationValue();
if (!string.IsNullOrEmpty(value) && value.Length == 5)
{
string firstPart = value.Substring(0, 2);
string secondPart = value.Substring(3, 3);
if (numbersRegex.IsMatch(firstPart) && lettersRegexnew.IsMatch(secondPart))
{
return ValidatorResult.Valid;
}
}
base.Text = "invalid value";
return base.GetFailedResult(ValidatorResult.FatalError);
}
protected override ValidatorResult GetMaxValidatorResult()
{
return base.GetFailedResult(ValidatorResult.FatalError);
}
public override string Name
{
get { return "testValidator"; }
}
}
}
Only people in certain roles, even get the option of forcing a save. Admins and I think people in the "Sitecore Developer" role.
As such, you are given the option of forcing a save through. This is normal behaviour.
Your regular editor users would not be able to save.

C# Windows Form search txt file for passed data and pass to textboxes

I'm relatively new to C# and have spent an inordinate amount of time trying to figure this out myself with no luck. Hoping you guys can help.
I have 2 windows forms. In the first form, the user enters a citation number. I want to take that citation number, search for it in an external text file, and then return all of the data in the row for that citation into separate textboxes.
The text file looks something like this:
S8729936 , 6JXV123 , 10/1/2015 , 10/31/2015 , PAID , 49.5
A7472601 , 2NXP234 , 10/12/2015 , 11/11/2015 , UNPAID , 99
W2041810 , 5JPB345 , 10/19/2015 , 11/18/2015 , UNPAID , 99
And the second form has 6 textboxes. I have it so that the citation number, let's say S8729936 is passed into the first textbox, but I cannot seem to figure out how to then search the text file for S8729936 and give me the rest of the data in the row inside the textboxes.
Here are some examples of things I've tried. I've been copying and pasting and then messing with code all day, so if the details don't seem to match, that's probably the reason.
public Form2(string citation)
{
InitializeComponent();
txtCitation2.Text = citation;
const string FILENAME = #"\Path\ProjectData.txt";
FileStream fsInFile = new FileStream(FILENAME, FileMode.Open, FileAccess.Read);
StreamReader srReader = new StreamReader(fsInFile);
const char CH_DELIM = ',';
string strRecordIn;
string[] strFields;
if (strFields != null)
{
strRecordIn = srReader.ReadLine();
strFields = strRecordIn.Split(CH_DELIM);
txtLicense2.Text = strFields[1];
}
strRecordIn = srReader.ReadLine();
srReader.Close();
fsInFile.Close();
}
No luck there, how about something along the lines of this:
string whole_file = File.ReadAllText(#"Path\ProjectData.txt");
whole_file = whole_file.Replace('\n', '\r');
string[] lines = whole_file.Split(new char[] { '\r' });
int num_rows = lines.Length;
int num_cols = lines[0].Split(',').Length;
string[,] values = new string[num_rows, num_cols];
for (int r = 0; r < num_rows; r++)
{
string[] line_r = lines[r].Split(',');
for (int c = 0; c < num_cols; c++)
{
values[r, c] = line_r[c];
}
}
txtLicense2.Text = lines[1];
Nope. Maybe something along the lines of this:
const string FILENAME = #"C:\Users\rfranklin\Documents\ProjectData.txt";
FileStream fsinfile = new FileStream(FILENAME, FileMode.Open, FileAccess.Read);
StreamReader srReader = new StreamReader(fsinfile);
const string CH_DELIM = " ,";
string strRecordIn;
string[] strFields = new string[10];
string citnum = citation;
bool found = false;
strRecordIn = srReader.ReadLine();
foreach(string x in strFields)
{
if (x == citation)
{
found = true;
break;
}
}
if (found)
{
txtLicense2.Text = strFields[1];
}
Still no luck. And on and on. It seems as though I'm mostly missing how to tell the program what to search for and I am not sure what else to do. Like I said, I've been Googling various ways to do it all day, but I can't seem to make anything work right.
I'm doing this in Visual Studio 2013, if that helps.
Any help would be immensely appreciated. Thanks.
If the number of lines in the CSV file is not too large (I wouldn't know what "too large" is), then you could leverage a few .NET constructs, such as Data Binding and Linq to achieve this.
For starters, I would create a class that implements INotifyPropertyChanged:
namespace Citations
{
public class Citation : INotifyPropertyChanged
{
public static Citation ParseLine(string line)
{
Citation cit = new Citation();
if (string.IsNullOrEmpty(line))
throw new ArgumentException("Invalid argument", nameof(line));
string[] vals = line.Split(',');
if (vals.Length != 6)
throw new ArgumentOutOfRangeException(nameof(line), "Invalid format");
cit.CitationNumber = vals[0].Trim();
cit.PlateNumber = vals[1].Trim();
cit.DateCreated = DateTime.Parse(vals[2].Trim());
cit.DateExpired = DateTime.Parse(vals[3].Trim());
cit.Status = vals[4].Trim();
cit.Amount = Decimal.Parse(vals[5].Trim());
return cit;
}
void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
string citationNumber;
public string CitationNumber
{
get
{
return citationNumber;
}
set
{
if (citationNumber != value)
{
citationNumber = value;
RaisePropertyChanged(nameof(CitationNumber));
}
}
}
string plateNumber;
public string PlateNumber
{
get
{
return plateNumber;
}
set
{
if (plateNumber != value)
{
plateNumber = value;
RaisePropertyChanged(nameof(PlateNumber));
}
}
}
DateTime dateCreated;
public DateTime DateCreated
{
get
{
return dateCreated;
}
set
{
if (dateCreated != value)
{
dateCreated = value;
RaisePropertyChanged(nameof(DateCreated));
}
}
}
DateTime dateExpired;
public DateTime DateExpired
{
get
{
return dateExpired;
}
set
{
if (dateExpired != value)
{
dateExpired = value;
RaisePropertyChanged(nameof(DateExpired));
}
}
}
string status;
public string Status
{
get
{
return status;
}
set
{
if (status != value)
{
status = value;
RaisePropertyChanged(nameof(Status));
}
}
}
Decimal amount;
public Decimal Amount
{
get
{
return amount;
}
set
{
if (amount != value)
{
amount = value;
RaisePropertyChanged(nameof(Amount));
}
}
}
}
}
This class is responsible for splitting a line from the CSV file, and converting it to an object of type Citation, which will be used to data bind the textboxes in Form2 later on.
Then in the first Form, I would simply read the file, and using some Linq operators, convert the file to a Dictionary of Citation objects, the key of the Dictionary being the Citation number:
private void button1_Click(object sender, EventArgs e)
{
string[] allLines = File.ReadAllLines("Citations.csv");
Dictionary<string, Citation> dict = allLines.Select(l => Citation.ParseLine(l)).ToDictionary(c => c.CitationNumber, k => k);
Citation cit = dict["W2041810"];
Form2 frm2 = new Form2();
frm2.SetCitation(cit);
frm2.ShowDialog();
}
In the code above, we're using the ToDictionary Linq operator to create a Disctionary from your Citation objects, and the Dictionat key is the Citation number.
Here I'm hardcoding one of the citations for lookup and passing to Form2, which would have a SetCitation method like this:
public void SetCitation(Citation citation)
{
this.citationBindingSource.DataSource = citation;
}
The code to Form2 is a bit difficult to show because I have used the Form designer to setup the data binding for each TextBox, and if I wanted to show that, I'd basically have to show the whole Form2.Designer.cs file.
Instead, I propose to guide you through the process of creating a Project DataSource, then drag & drop the TextBoxes onto Form2 from the Data Sources dialog in Visual Studio.
So, after adding the Citation class to your solution, make sure to compile at leat once so that the "Add data source" wizard will pick that class up as a possible data source.
Then, make sure the Data Sources dialog is displayed by going to View > Other Windows > Data Sources (assuming Visual Studio 2015 here).
From the Data Sources dialog, click the "Add New Data Source button" tolaunch the Data SOurce Configuration Wizard. From the list of possible data sources, you will choose "Object":
Then click the Next button. From the next Wizard step, you will select the Citation class:
and then click the Finish button.
In the Data Sources dialog, you should now have something like this:
From this Data Sources dialog, you can now drag & drop the individual fields onto Form2, which should give you something like this:
You will also notice in the Component tray of Form2, a BindingSource object has been added:
Under the hood, Visual Studio will have set all the Data Binding for your Citation object properties to be displayed in the corresponding TextBox.
This is the "glue" that makes it possible to call Form2.SetCitation() with a Citation object, and have all the fields displayed where they should.
I know this is quite a mouthful to chew on, but believe me, once you understand the principles behind this, you will not want to go back to the kind of spagethi code that you started implementing (no offense, we've all been there).
If you would like me to clarify any specific section of my answer, just let me know, and I'll edit accordingly.
Cheers
A simple winForm version:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
List<string> matchedList = new List<string>();
public Form1(string citation)
{
InitializeComponent();
string filePath = #"C:\Users\rfranklin\Documents\ProjectData.txt";
string[] linesArr = File.ReadAllLines(filePath);
//find matches
foreach(string s in linesArr)
{
if(s.Contains(citation))
{
matchedList.Add(s); //matched
}
}
//output
foreach(string s in matchedList)
{
Console.WriteLine(s); //write to console
//or output to wherever you wish, eg.
//richTextBox.Text += s + "\n";
}
}
}
}
Note that Form1 needs to be called from somewhere else and take in citation argument. To test it standalone, change it to
public Form1()
{
InitializeComponent();
string citation = "S8729936";
The suggestion from #interceptwind did it for me. Inside the //output section of the code he provided, I basically just created a second array from the matched line, with the elements separated by the comma. The code looks like this:
//output
foreach (string s in matchedList)
{
string citationLine = s;
string[] lineData = citationLine.Split(',');
txtLicense2.Text = lineData[1];
txtIssued2.Text = lineData[2];
txtDue2.Text = lineData[3];
txtStatus2.Text = lineData[4];
txtAmount2.Text = lineData[5];
}
This allowed me to put the data in the textboxes I needed. Thank you all for the assistance!

Remove the last entered character in combobox

I am here with another problem.
I have setup my comboBox such that it accepts only those characters which matches with the name of any items in the comboBoxItems.
Now here I am stuck with a problem. Please have a look at my code then I will explain you the problem :
private void myComboBox_KeyUp(object sender, KeyEventArgs e)
{
// Get the textbox part of the combobox
TextBox textBox = cbEffectOn.Template.FindName("PART_EditableTextBox", cbEffectOn) as TextBox;
// holds the list of combobox items as strings
List<String> items = new List<String>();
// indicates whether the new character added should be removed
bool shouldRemoveLastChar = true;
for (int i = 0; i < cbEffectOn.Items.Count; i++)
{
items.Add(cbEffectOn.Items.GetItemAt(i).ToString());
}
for (int i = 0; i < items.Count; i++)
{
// legal character input
if (textBox.Text != "" && items.ElementAt(i).StartsWith(textBox.Text))
{
shouldRemoveLastChar = false;
break;
}
}
// illegal character input
if (textBox.Text != "" && shouldRemoveLastChar)
{
textBox.Text = textBox.Text.Remove(textBox.Text.Length - 1);
textBox.CaretIndex = textBox.Text.Length;
}
}
In the last if condition I am removing the last character from the combobox. But user can use arrow keys or mouse to change the position of the cursor and enter the text at the middle of the text.
So if by entering a character at the middle of the text if the text becomes invalid I mean if it does not match the Items in the ComboBox then I should remove the last entered character. Can anybody suggest me how to get the last inserted character and remove it?
Update :
string OldValue = "";
private void myComboBox_KeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = cbEffectOn.Template.FindName("PART_EditableTextBox", cbEffectOn) as TextBox;
List<String> items = new List<String>();
for (int i = 0; i < cbEffectOn.Items.Count; i++)
{
items.Add(cbEffectOn.Items.GetItemAt(i).ToString());
}
OldValue = textBox.Text;
bool shouldReplaceWithOldValue = true;
string NewValue = textBox.Text.Insert(textBox.CaretIndex,e.Key.ToString()).Remove(textBox.CaretIndex + 1,textBox.Text.Length - textBox.CaretIndex);
for (int i = 0; i < items.Count; i++)
{
// legal character input
if (NewValue != "" && items.ElementAt(i).StartsWith(NewValue, StringComparison.InvariantCultureIgnoreCase))
{
shouldReplaceWithOldValue = false;
break;
}
}
//// illegal character input
if (NewValue != "" && shouldReplaceWithOldValue)
{
e.Handled = true;
}
}
Here I have tried to move all the code in KeyDown event to solve the above problem. This code works just fine but have 1 problem.
If I have any item named Birds & Animals then After typing Birds and a space I cannot type &.
I know what is the problem but don't know the solution.
The Problem is : To type & I have to press shift key and then press the 7 key. But both are sent as different keys.
Solutions that I think about :
1) I should move my code to KeyUp event. But here the problem of long press and fast typing will arise.
2) I think I should replace e.Key with something. But don't know what.
I'm not sure if this is what you're trying to do but I feel like you're trying to do what we typically see in visual studio Intellisense fitlering out results as we type.
Instead of removing the keystrokes, you should be using the validation mechanisms that WPF provides. Here's a sample of how this could work.
Scenarios covered:
Input matches a combox item completely: TypedInput & SelectedItem both show full match.
Input matches some element partially: TypedInput shortlists the popup list. The binding shows the matching text while SelectedItem remains null.
Input doesn't match any item in list either from start or at some
random point: user is visually given feedback (with possibility to
add additional feedback information) with typical red outline. The
TypedInput remains at last valid entry, SelectedItem may or may
not be null depending on if the last TypedInput matched any item or not.
Full Code:
MainWindow.xaml
<Window x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:Sample"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Source={x:Static l:MainWindowViewModel.CurrentInstance}}">
<StackPanel>
<TextBlock>
<Run Text="Typed valid text" />
<Run Text="{Binding TypedText}"/>
</TextBlock>
<TextBlock>
<Run Text="Valid SelectedItem" />
<Run Text="{Binding SelectedItem}"/>
</TextBlock>
<ComboBox ItemsSource="{Binding FilteredItems}" IsEditable="True" IsTextSearchEnabled="False" SelectedItem="{Binding SelectedItem}">
<ComboBox.Text>
<Binding Path="TypedText" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<l:ContainsValidationRule />
</Binding.ValidationRules>
</Binding>
</ComboBox.Text>
</ComboBox>
</StackPanel>
</Window>
MainWindow.xaml.cs
namespace Sample
{
public partial class MainWindow { public MainWindow() { InitializeComponent(); } }
}
ContainsValidationRule.cs -- Meat of the solution
namespace Sample
{
using System.Globalization;
using System.Linq;
using System.Windows.Controls;
public class ContainsValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var result = MainWindowViewModel.CurrentInstance.Items.Any(x => x.ToLower(cultureInfo).Contains((value as string).ToLower(cultureInfo)));
return new ValidationResult(result, "No Reason");
}
}
}
MainWindowViewModel - Supporting ViewModel Singleton
namespace Sample
{
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
public sealed class MainWindowViewModel : INotifyPropertyChanged
{
private string _typedText;
private string _selectedItem;
private static readonly MainWindowViewModel Instance = new MainWindowViewModel();
private MainWindowViewModel()
{
Items = new[] { "Apples", "Apples Green", "Bananas", "Bananas & Oranges", "Oranges", "Grapes" };
}
public static MainWindowViewModel CurrentInstance { get { return Instance; } }
public string SelectedItem
{
get { return _selectedItem; }
set
{
if (value == _selectedItem) return;
_selectedItem = value;
OnPropertyChanged();
}
}
public string TypedText
{
get { return _typedText; }
set
{
if (value == _typedText) return;
_typedText = value;
OnPropertyChanged();
OnPropertyChanged("FilteredItems");
}
}
public IEnumerable<string> Items { get; private set; }
public IEnumerable<string> FilteredItems
{
get
{
return Items == null || TypedText == null ? Items : Items.Where(x => x.ToLowerInvariant().Contains(TypedText.ToLowerInvariant()));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Instead of KeyUp event, subscribe to TextChanged event on your ComboBox Textbox. In event handler you can get the offset where the change has occured. You can use your validation logic inside the hanlder and delete the character at the offset if it makes Text invalid.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
TextBox textBox = cbEffectOn.Template.FindName("PART_EditableTextBox", cbEffectOn) as TextBox;
textBox.TextChanged += new TextChangedEventHandler(textBox_TextChanged);
}
void textBox_TextChanged(object sender, TextChangedEventArgs e)
{
int index = e.Changes.First().Offset;
}
Have you considered using a string variable to hold the last legal text value in the text box portion of the combo box?
Initially, this string would be empty, as the user has not typed anything yet, then as each KeyUp event is handled, if an invalid character is input, then the previous string value is used to replace the text of the text box; otherwise the previous string value is now updated with the new complete string; awaiting anymore input by the user.

Programmatically adding two buttons to each row as it loads

I've created a ListView in a new WPF window and also a function that populates the ListView when it is called. This function just takes the URL of my web server where I've stored the data, increments the "id" and gets the data and stores it in the ListView. Therefore it populates the ListView with a certain number of items.
The problem I'm facing is that I want to add two buttons, ON & OFF, to each ListView item as it gets populated programmatically. i.e, if 16 items are added, I want 2 buttons for each item, and if it's 12 items, the similar procedure. Here's my code:
namespace user_login
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Window1 W = new Window1();
public MainWindow()
{
InitializeComponent();
}
public void populate()
{
int i;
int num = 16;
for (i = 1; i <= num; i++)
{
string val = Convert.ToString(i);
string currentUrl = "http://xpleria.com/devices.php?query=dev&id=";
string newUrlWithChangedSort = ReplaceQueryStringParam(currentUrl, "id", val);
string result = getcontent(newUrlWithChangedSort);
W.list1.Items.Add(result);
}
}
public string getcontent(string URL)
{
string content = "";
// Get HTML data
WebClient client = new WebClient();
try
{
content = client.DownloadString(URL);
}
catch (Exception)
{
System.Windows.Forms.MessageBox.Show("No Connection detected!!!");
}
return content;
}
public static string ReplaceQueryStringParam(string currentPageUrl, string paramToReplace, string newValue)
{
string urlWithoutQuery = currentPageUrl.IndexOf('?') >= 0
? currentPageUrl.Substring(0, currentPageUrl.IndexOf('?'))
: currentPageUrl;
string queryString = currentPageUrl.IndexOf('?') >= 0
? currentPageUrl.Substring(currentPageUrl.IndexOf('?'))
: null;
var queryParamList = queryString != null
? HttpUtility.ParseQueryString(queryString)
: HttpUtility.ParseQueryString(string.Empty);
if (queryParamList[paramToReplace] != null)
{
queryParamList[paramToReplace] = newValue;
}
else
{
queryParamList.Add(paramToReplace, newValue);
}
return String.Format("{0}?{1}", urlWithoutQuery, queryParamList);
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
string user = textbox1.Text;
string password = textbox2.Password;
string currentUrl = "http://xpleria.com/login.php?query=login&user=wcam&pass=wireless";
string newUrlWithChangedSort = ReplaceQueryStringParam(currentUrl, "user", user);
string newUrl = newUrlWithChangedSort;
string FinalUrl = ReplaceQueryStringParam(newUrl, "pass", password);
string result= getcontent(FinalUrl);
string value = result.Substring(0, 8);
string invalid = "xpleria0";
string valid = "xpleria1";
if (value.Equals(invalid))
{
System.Windows.MessageBox.Show("The Username and/or Password you have entered is invalid, please try again");
}
else if (value.Equals(valid))
{
string sessionID = result.Substring(8, 32);
System.Windows.MessageBox.Show("HI, WELCOME CLETA");
this.Close();
using (new user_login.loading.PleaseWait(this.Location))
{
W.Show();
populate();
}
}
}
public System.Drawing.Point Location { get; set; }
}
}
I'm going to recommend you take a step back and start giving some serious consideration to organizing your code. I realize this isn't an answer to the question you asked but it is the answer to the question you should be asking.
First of all, all code relating to the retrieval of these items from the URL should be moved into a class of some kind. This class should accept the URL string as a constructor parameter and gather all the appropriate data. You should then create another class which you will use to populate with the data for each individual item and then expose this list. By the time you're done the code in your window should little more complex than:
var ItemsGetter = new ItemsGetter(URL);
foreach(var Item in ItemsGetter.Items)
{
// Populate the ListView
}
Once you're done with that I recommend you create a UserControl. User controls are extremely useful in situations where you need to represent a dynamic number of data entities each with their own set of controls which allow operations to be performed on each one. You should create a UserControl with a label and the two buttons you need. The UserControl's constructor should expect a parameter of the data type you created to represent each one of your classes. From there you can have the buttons operate on the data type as necessary.
Finally, you'll probably need a way to have the UserControl interact with the Window. Say for example one of your buttons is "Delete". You'd probably want the item to disappear from the list once the operation is complete. Don't be tempted to tie in your control with the Window by passing it as a parameter or something. Instead, read up on Action events and learn how you can create an event on the user control which you bind in the foreach loop of the Window when you're populating the list view. When the UserControl has completed the delete operation triggered by the button you can raise the UserControl's event which will prompt the Window to remove the control from the List View.
Last but not least, NAME YOUR CONTROLS.
Hopefully this helps.

Categories

Resources