I have a combobox in my application, where items are loaded asynchronously depending on a search text you can enter in the text field.
This works fine, but every time the text of the first item is automatically selected during updating the datasource of the combobox.
This leads to unintended behaviour, because I need to have the search text entered by the user to stay in the textfield of the combobox until a selection is done by the user and not automatically overwrite the text with the first entry.
This is my code:
public partial class ProductGroupDescription : UserControl, Interfaces.TabPages.ITabPageProductGroupDescription
{
private Services.IProductGroupDescriptionService _ApplicationService;
public BindingList<ProductGroup> ProductGroups { get; set; } = new BindingList<ProductGroup>();
public string ProductGroupSearchText { get; set; } = string.Empty;
public ProductGroupDescription(Services.IProductGroupDescriptionService applicationService)
{
InitializeComponent();
InitialSetupControls();
_ApplicationService = applicationService;
}
public void InitialSetupControls()
{
var pgBindingSource = new BindingSource();
pgBindingSource.DataSource = ProductGroups;
Cbo_ProductGroup.DataSource = pgBindingSource.DataSource;
Cbo_ProductGroup.DataBindings.Add("Text", ProductGroupSearchText, "");
}
private async void Cbo_ProductGroup_TextChanged(object sender, EventArgs e)
{
if (Cbo_ProductGroup.Text.Length >= 2)
{
ProductGroupSearchText = Cbo_ProductGroup.Text;
Cbo_ProductGroup.SelectedIndex = -1;
bool withStopFlagged = Chk_StopFlag_PGs_Included.Checked;
List<ProductGroup> list = await _ApplicationService.GetProductGroupBySearchString(ProductGroupSearchText, withStopFlagged);
if (list != null && list.Count > 0)
{
ProductGroups.Clear();
list.ForEach(item => ProductGroups.Add(item));
Cbo_ProductGroup.DroppedDown = Cbo_ProductGroup.Items.Count > 0 && Cbo_ProductGroup.Focused;
}
}
}
}
I tried to set Cbo_ProductGroup.SelectedIndex = -1, but it does not solve my issue here.
I also saw this on SO: Prevent AutoSelect behavior of a System.Window.Forms.ComboBox (C#)
But is there really no simpler solution to this issue?
I got it to work now.
It worked, when I removed the binding of the text field of the combobox
Cbo_ProductGroup.DataBindings.Add("Text", ProductGroupSearchText, "");
and set the new (old) value directly to the text field of the combobox.
Cbo_ProductGroup.Text = searchText;
In this case, a new event Text_Changed is fired and so the application has a infinite loop. So I used a property (ShouldTextChangedEventBeIgnored), if the Text_Changed event should be ignored.
Thanks to #CaiusJard for many hints in the comments.
This is my final code:
public partial class ProductGroupDescription : UserControl, Interfaces.TabPages.ITabPageProductGroupDescription
{
private ApplicationLogic.Interfaces.Services.IProductGroupDescriptionService _ApplicationService;
public BindingList<ProductGroup> ProductGroups { get; set; } = new BindingList<ProductGroup>();
public bool ShouldTextChangedEventBeIgnored { get; set; } = false;
public ProductGroupDescription(ApplicationLogic.Interfaces.Services.IProductGroupDescriptionService applicationService)
{
_ApplicationService = applicationService;
InitializeComponent();
InitialSetupControls();
}
public void InitialSetupControls()
{
var pgBindingSource = new BindingSource();
pgBindingSource.DataSource = ProductGroups;
Cbo_ProductGroup.DataSource = pgBindingSource.DataSource;
}
private async Task<List<ProductGroup>> LoadProductGroupItems(string searchText)
{
bool withStopFlagged = Chk_StopFlag_PGs_Included.Checked;
return await _ApplicationService.GetProductGroupBySearchString(searchText, withStopFlagged);
}
private async Task SetProductGroupSearchBoxItems(List<ProductGroup> list, string searchText)
{
await Task.Run(() =>
{
if (list != null && list.Count > 0)
{
ShouldTextChangedEventBeIgnored = true;
Cbo_ProductGroup.Invoke((c) =>
{
ProductGroups.Clear();
list.ForEach(item => ProductGroups.Add(item));
c.DroppedDown = c.Items.Count > 0 && c.Focused;
c.Text = searchText;
c.Select(c.Text.Length, 0);
});
ShouldTextChangedEventBeIgnored = false;
}
});
}
private async void Cbo_ProductGroup_TextChanged(object sender, EventArgs e)
{
try
{
if (Cbo_ProductGroup.Text.Length >= 2 && ShouldTextChangedEventBeIgnored == false)
{
string searchText = Cbo_ProductGroup.Text;
List<ProductGroup> list = await LoadProductGroupItems(Cbo_ProductGroup.Text);
await SetProductGroupSearchBoxItems(list, searchText);
}
}
catch(Exception ex)
{
System.Diagnostics.Trace.Write(ex);
}
}
}
Related
this below to sample code;
private ExampleStatus _status;
public ExampleStatus status
{
get
{
if (_status == null) _status = new ExampleStatus();
//if (_status.receivedData) _status.receivedData = false; //this line is incorrect !
return _status;
}
}
public class ExampleStatus
{
public int Id { get; set; }
public string Name { get; set; }
public bool receivedData { get; set; }
//I don't want to use this method
public void Clear()
{
Id = 0;
Name = string.Empty;
receivedData = false;
}
}
int stateType = 0;
void ContinuousLoop(ExampleStatus statusObj)
{
while (true)
{
//I don't want to use the options below
//statusObj.Clear();
//or
//statusObj = new ExampleStatus();
if (stateType == 0)
{
statusObj.Id = 0;
statusObj.Name = "Firs Status";
}
else if (stateType == 1)
{
statusObj.Id = 1;
statusObj.Name = "Second Status";
statusObj.receivedData = true;
}
else if (stateType == 2)
{
statusObj.Id = 2;
statusObj.Name = "Third Status";
}
}
}
void RunThread()
{
var t1 = new Thread(() =>
{
ContinuousLoop(status);
});
t1.Start();
}
Is it possible to set default values without a method or new instance, as shown in the example?
Actually that's why I'm asking this question:
I will use the class I have defined in many places. I will need to add a block of code, such as the Clear method, to every place I use it.
I'm also curious about one more thing. If I assign a new instance every time to reset my objects, does this cause problems in memory?
I know more or less how garbage collections work. However, they say that in practice it does not work as said in theory.
So if I add "IDisposable" to my Class, it would tell the garbage collector: Welcome, I'm a litter. Will you take me too?
// The class for the search query
public class SearchQueries
{
List<data> list = new List<data>();
string response;
// The method that return the list after it is set
public List<data> GetData()
{
return list;
}
// The method that do the searching from the google API service
public async void SetData()
{
// The problem starts here, when i instantiate the search class in this class in other to get the value of the text in the autosuggestbox for my query, it crashes whenever i try to launch the page. it works fine whenever i give the address default data e.g string address = "London", the page open when i launch it and give me London related result whenever i type in the autosuggestbox.
Search search = new Search();
string address = search.Address;
list.Clear();
// Note the tutorial i used was getting the data from a local folder, but i'm trying to get mine from Google API
string dataUri = "https://maps.googleapis.com/maps/api/place/autocomplete/json?key=AIzaSyDBazIiBn2tTmqcSpkH65Xq5doTSuOo22A&input=" + address;
string Api = System.Uri.EscapeDataString(dataUri);
HttpClient client = new HttpClient();
client.Timeout = TimeSpan.FromMilliseconds(1000);
try
{
response = await client.GetStringAsync(Api);
for (uint i = 0; i < jsonarray.Count; i++)
{
string json_string_object = jsonarray.GetObjectAt(i)["description"].ToString();
list.Add(new data() { name = json_string_object });
}
}
catch (TimeoutException e)
{
ContentDialog myDlg = new ContentDialog()
{
PrimaryButtonText = "OK"
};
myDlg.Content = e.ToString();
}
}
// Method to get matched data
public IEnumerable<data> getmatchingCustomer(string query)
{
return list.Where(c => c.name.IndexOf(query, StringComparison.CurrentCultureIgnoreCase) > -1).OrderByDescending(c => c.name.StartsWith(query, StringComparison.CurrentCultureIgnoreCase));
}
// constructor for returning the SetData() method
public SearchQueries()
{
// It points to this method whenever the application crash, with the notification of infinite loop or infinite recursion
SetData();
}
}
// The Main Class of the page
public sealed partial class Search : Page
{
public string theaddress { get; set; }
SearchQueriess queries = new SearchQueriess();
public Search()
{
this.InitializeComponent();
myMap.Loaded += MyMap_Loaded;
theaddress = locationAddress.Text;
}
// The text change method of the autosuggest box.
private async void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
if (sender.Text.Length > 1)
{
var marchingData = queries.getmatchingCustomer(sender.Text);
sender.ItemsSource = marchingData.ToList();
}
else
{
sender.ItemsSource = new string[] { "No suggestion...." };
}
}
}
}
I'm getting a few errors and also my code is unfinished. I was using another Stackoverflow question to set this up to begin with but it wasn't fit to my needs.
I have three text files which the data is split by commas such as "Name,25,25.6" so string, int, decimal. I have all three text files that have three columns like that, same data types, but just different names/numbers.
I have three different list boxes that I want to split them into but I'm having trouble getting the three different split list items to get into three different list boxes. I'll copy and paste all the code I have. I am also using a combo box to allow the user to select the file they want to load into the combo box which I believe I got it right.
The errors I get are in the displayLists(), it says on the lstItemName.DataSource = Inventory; line that Inventory does not exist in the current context. There are also a plenitude of other errors.
Any help will be appreciated, I'll copy and paste my code. I have a Windows Form and I'm using Visual Studio Express 2012 in C#
namespace TCSCapstone
{
public partial class frmInventory : Form
{
public frmInventory()
{
InitializeComponent();
}
string cstrItemName;
int cintNumberOfItems;
decimal cdecPrice;
decimal cdecTotalPrices;
string selectedList = "";
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
selectedList = this.cmbList.GetItemText(this.cmbList.SelectedItem);
if (selectedList == "Creative Construction")//if the selected combo
box item equals the exact string selected
{
selectedList = "creative"; //then the string equals creative,
which is creative.txt but I add the .txt in the btnLoadInfo method
} else if (selectedList == "Paradise Building")
{
selectedList = "paradise";//this is for paradise.txt
}
else if (selectedList == "Sitler Construction")
{
selectedList = "sitler";//this is for sitler.txt
}
else
{
MessageBox.Show("Please select one of the items.");
}
}
private void btnLoadInfo_Click(object sender, EventArgs e)
{
List<frmInventory> Inventory = new List<frmInventory>();
using (StreamReader invReader = new StreamReader(selectedList +
".txt"))
{
while (invReader.Peek() >= 0)
{
string str;
string[] strArray;
str = invReader.ReadLine();
strArray = str.Split(',');
frmInventory currentItem = new frmInventory();
currentItem.cstrItemName = strArray[0];
currentItem.cintNumberOfItems = int.Parse(strArray[1]);
currentItem.cdecPrice = decimal.Parse(strArray[2]);
Inventory.Add(currentItem);
}
}
displayLists();
}//end of btnLoadInfo
void displayLists()
{
int i;
lstItemName.Items.Clear();
lstNumberOfItems.Items.Clear();
lstPrice.Items.Clear();
lstTotalPrices.Items.Clear();
lstItemName.DataSource = Inventory;
lstItemName.ValueMember = "cstrItemName";
lstItemName.DisplayMember = "cintNumberOfItems";
}
}//end of frmInventory
}//end of namespace
I do not know if this is exactly what you need, but try something like this:
public partial class Form2 : Form
{
List<Inventory> inventory;
public Form2()
{
InitializeComponent();
}
public void ReadFiles()
{
if (inventory == null)
inventory = new List<Inventory>();
using (TextReader r = new StreamReader("file.txt"))
{
string line = null;
while ((line = r.ReadLine()) != null)
{
string[] fields = line.Split(',');
Inventory obj = new Inventory();
obj.Name = fields[0];
obj.Qtd = Convert.ToInt32(fields[1]);
obj.Price = Convert.ToInt32(fields[2]);
inventory.Add(obj);
}
}
SetDataSourceList();
}
public void SetDataSourceList()
{
listBox1.DisplayMember = "Name";
listBox2.DisplayMember = "Qtd";
listBox3.DisplayMember = "Price";
listBox1.DataSource =
listBox2.DataSource =
listBox3.DataSource =
inventory;
}
}
public class Inventory
{
public string Name { get; set; }
public int Qtd { get; set; }
public decimal Price { get; set; }
}
I am creating varied number of MvxSpinners programmatically. The number of the MvxSpinners generated cannot be predetermined. It is determined by the user input.
I have a List<Beneficiary>. Each MvxSpinner is meant to update each Beneficiary in the collection.
Since I cannot determine the number of MvxSpinner (which corresponds to the count of the Beneficiary in the collection) to be generated, I am forced to have one ICommand to handle all the HandleSelectedItem event of the MvxSpinners.
The Challenge
I am having difficulty determining the index of the List<Beneficiary> to update depending on the MvxSpinner the user clicked.
An Example
let
var BeneficiaryList=new List<Beneficiary>()
If there are 5 Beneficiary object in the collection, 5 MvxSpinner will be generated.
If the user selects a MVXSpinner which is meant to update index 2 of the collection, how do i determine the index of Beneficary to update?
What I have tried
private IList<Beneficiary> _beneficiaryList;
public IList<Beneficiary> BeneficiaryList
{
get { return _beneficiaryList; }
set { _beneficiaryList= value; RaisePropertyChanged(() => BeneficiaryList); }
}
public ICommand UpdateBeneficiary=> new MvxCommand<Beneficiary>(item =>
{
//item is of type Beneficiary
//But I do not know which index of BeneficiaryList to update
});
Your help will be deeply appreciated.
You probably need a List of ICommands too, one for each spinner. Something like this in your view model...
private IList<ICommand> _commands;
public IList<ICommand> Commands {
get {
if (_commands == null) {
_commands = BeneficiaryList.Select(x => new MvxCommand<Beneficiary>(item => {
...
}));
}
return _commands;
}
}
And set up your bindings like this (assuming you've got a list of spinners)
for (int i = 0; i < spinners.Count; i++) {
var spinner = spinners[i];
set.Bind (spinner).To(vm => vm.Commands[i]);
}
Well, it is interesting to answer my own question.
What I did was to give each Spinner a unique ID that corresponds to the index of the collection.
I created a custom Spinner called MvxSpinnerIndexer extending MvxSpinner (I really do not think it matters. You can just extend Spinner). MvxSpinnerIndexer retrieved the Id and the SelectedItem and then placed the two into a Dictionary
Here is the source for MvxSpinnerIndexer
public class MvxSpinnerIndexer : Spinner
{
public MvxSpinnerIndexer(Context context, IAttributeSet attrs)
: this(
context, attrs,
new MvxAdapter(context)
{
SimpleViewLayoutId = global::Android.Resource.Layout.SimpleDropDownItem1Line
})
{ }
public MvxSpinnerIndexer(Context context, IAttributeSet attrs, IMvxAdapter adapter)
: base(context, attrs)
{
var itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId(context, attrs);
var dropDownItemTemplateId = MvxAttributeHelpers.ReadDropDownListItemTemplateId(context, attrs);
adapter.ItemTemplateId = itemTemplateId;
adapter.DropDownItemTemplateId = dropDownItemTemplateId;
Adapter = adapter;
SetupHandleItemSelected();
}
public new IMvxAdapter Adapter
{
get { return base.Adapter as IMvxAdapter; }
set
{
var existing = Adapter;
if (existing == value)
return;
if (existing != null && value != null)
{
value.ItemsSource = existing.ItemsSource;
value.ItemTemplateId = existing.ItemTemplateId;
}
base.Adapter = value;
}
}
[MvxSetToNullAfterBinding]
public IEnumerable ItemsSource
{
get { return Adapter.ItemsSource; }
set { Adapter.ItemsSource = value; }
}
public int ItemTemplateId
{
get { return Adapter.ItemTemplateId; }
set { Adapter.ItemTemplateId = value; }
}
public int DropDownItemTemplateId
{
get { return Adapter.DropDownItemTemplateId; }
set { Adapter.DropDownItemTemplateId = value; }
}
public ICommand HandleItemSelected { get; set; }
public int ViewId { get; set; }
private void SetupHandleItemSelected()
{
ItemSelected += (sender, args) =>
{
//sender.
var control = (MvxSpinnerIndexer)sender;
var controlId = control.Id;
var position = args.Position;
HandleSelected(position, controlId);
};
}
protected virtual void HandleSelected(int position, int? controlId)
{
var item = Adapter.GetRawItem(position);
var content = new Dictionary<string, object> {{"Index", controlId}, {"SelectedItem", item}};
//var param = new ListItemWithIndexModel { Index = controlId, SelectedItem = item };
if (HandleItemSelected == null
|| item == null
|| !HandleItemSelected.CanExecute(content))
return;
HandleItemSelected.Execute(content);
}
}
In your ViewModel
public ICommand SpinnerSelected => new MvxCommand<Dictionary<string, object>>(item =>
{
var selectedItem = item["SelectedItem"] as ListItemModel;//Cast to the actual model
var index = item["Index"] as int?;//The index of the collection to update
});
I believe this will be useful to the community.
I just want my ComboBox to show me the
FullName of objects in List(Curator),
but it show me the same "object.FullName" multiple times :-(
-
Basically, it work cause it show me the FullName of ONE of the Curator,
and the good amount of times,
but it show me the same ONE !
public partial class SGIArt : Form
{
public static Gallery gal = new Gallery(); // from a dll i made
List<Curator> curList = new List<Curator>();
public SGIArt()
{
InitializeComponent();
comboCur.DataSource = curList;
comboCur.ValueMember = null;
comboCur.DisplayMember = "FullName";
UpdateCurList();
}
public void UpdateCurList()
{
curList.Clear();
foreach (Curator cur in gal.GetCurList())
// from the same dll : Curators curatorsList = new Curators();
{
curList.Add(cur);
}
}
private void comboCur_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboCur.SelectedValue != null)
{
//show info in textBox (that work fine)
}
}
}
Curator class :
public class Curator : Person
{
private int id;
private double commission;
const double commRate = 0.25;
private int assignedArtists = 0;
public int CuratorID
{
get
{
return id;
}
set
{
id = value;
}
}
...
public Curator()
{
}
public Curator(string First, string Last, int curID)
: base(First, Last) // from : public abstract class Person
{
id = curID;
commission = 0;
assignedArtists = 0;
}
Edit: You might be looking for this answer.
I do not see the FullName member in your code snippet. I think you are looking for something like this:
List<Curator> curList = new List<Curator>();
public SGIArt()
{
InitializeComponent();
comboCur.DataSource = datasource;
comboCur.ValueMember = null;
comboCur.DisplayMember = "FullName";
UpdateCurList();
}
List<string> datasource()
{
List<string> datasource = new List<string>();
foreach(Curator curator in curList)
{
datasource.Add(curator.FullName)//this assume FullName is an accesible member of the Curator class and is a string.
}
return datasource;
}
The comboBox shows you object.FullName, because this is what you are telling it. The curList is empty at the time when you bind it.
You can update your list before using it:
public SGIArt()
{
InitializeComponent();
UpdateCurList();
comboCur.DataSource = curList;
comboCur.ValueMember = null;
comboCur.DisplayMember = "FullName";
}