Filtering a ListBox - c#

I have a ListBox named lsbEntities. I want to filter it's items based on some selected radio button.
The code below is kind of pseudo, is their a better approach?
private List<string> _listBoxItemsToFilter;
private Thread tFilterEntityList;
enum EntityType
{
Vehicle,
Facility
}
private void FilterEntityList(EntityType entityType)
{
_listBoxItemsToFilter = new List<string>();
Dictionary<string,string> entitiesAndClassTypes;
List<string> listBoxItems = new List<string>();
for(int i = 0; i < lsbEntities.Count; i++)
{
//object listItem = lsbEntities.Items[i];
listBoxItems.Add(lsbEntities[i].ToString());
}
// get associated types
entityClassTypes = _controlFacade.GetClassTypes(listBoxItems);
foreach (KeyValuePair<string,string>
entityAndClass in entitiesAndClassTypes)
{
classType = entityAndClass.Value;
if(classType != entityType)
{
_listBoxItemsToFilter.Add(entityAndClass.Key);
}
}
RemoveFilterFromEntityListBox();
AddFilterToEntityListBox();
}
private void AddFilterToEntityListBox()
{
// DELEGATE NEEDED TO MODIFY LISTBOX FROM THREAD
foreach(string listBoxItem in _listBoxItemsToFilter)
{
if(lsbEntities.Contains(listBoxItem)
{
// REMOVE WITH DELEGATE
}
}
}
private void RemoveFilterFromEntityListBox()
{
// DELEGATE NEEDED TO MODIFY LISTBOX FROM THREAD
if(_listBoxItemsToFilter != null)
{
foreach(string listBoxItem in _listBoxItemsToFilter)
{
if(!lsbEntities.Contains(listBoxItem)
{
// REMOVE WITH DELEGATE
}
}
}
}
// EXAMPLE CALL WHEN CLICKING RADIO-BUTTON
private void rbVehicles_CheckedChanged(object sender, EventArgs e)
{
switch (rbVehicles.Checked)
{
case (true):
{
object entityType = (object)EntityType.Vehicle;
tFilterEntityList = new Thread(FilterEntityList(entityType));
tFilterEntityList.IsBackground = true;
tFilterEntityList.Start();
//FilterEntityList(EntityType.Vehicle);
break;
}
}
}
I have only included an example of selecting to filter everything but VehicleS. The same approach would be used for the Facility class, where the thread would be re-instantiated.

Here is a simple example showing one way to filter items in a ListBox. This could be improved by using a ListView or DataGridView in VirtualMode. It is very unclear to me what you are trying to do, so if this is not helpful I will remove it.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
public class Form1 : Form
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
List<Entity> items = new List<Entity>()
{
new Entity(EntityType.Vehicle, "Car"),
new Entity(EntityType.Vehicle, "Aeroplane"),
new Entity(EntityType.Vehicle, "Truck"),
new Entity(EntityType.Vehicle, "Bus"),
new Entity(EntityType.Facility, "Garage"),
new Entity(EntityType.Facility, "House"),
new Entity(EntityType.Facility, "Shack"),
};
ListBox listBox;
ComboBox comboBox;
public Form1()
{
Text = "Filtering Demo";
ClientSize = new Size(500, 320);
Controls.Add(listBox = new ListBox
{
Location = new Point(10, 10),
Size = new Size(200, 300),
});
Controls.Add(comboBox = new ComboBox
{
Location = new Point(230, 10),
DropDownStyle = ComboBoxStyle.DropDownList,
Items = { "All", EntityType.Vehicle, EntityType.Facility },
SelectedIndex = 0,
});
comboBox.SelectedIndexChanged += UpdateFilter;
UpdateFilter(comboBox, EventArgs.Empty);
}
void UpdateFilter(object sender, EventArgs e)
{
var filtered = items.Where((i) => comboBox.SelectedItem is string || (EntityType)comboBox.SelectedItem == i.EntityType);
listBox.DataSource = new BindingSource(filtered, "");
}
}
enum EntityType { Vehicle, Facility, }
class Entity : INotifyPropertyChanged
{
public string Name { get; private set; }
public EntityType EntityType { get; private set; }
public Entity(EntityType entityType, string name) { EntityType = entityType; Name = name; }
public override string ToString() { return Name ?? String.Empty; }
// Implementing INotifyPropertyChanged to eliminate (caught) BindingSource exceptions
public event PropertyChangedEventHandler PropertyChanged;
}

Related

SOLVED Datagridview Reload after Button click using Entity Framework?

I got a datagridview and per Button I can Add items, and now I want to see in the datagridview that something changed.
I tried so many thing. this.Refresh(); this.Invalidate(); nothing works,
I found Application.Restart(); but I am not happy with this solution.
Is there anything else??
var contex = new frachtkostenEntities();
try
{
var x = new Kunden()
{
Kundenname = first,
Zielort = second,
};
contex.Kunden.Add(x);
contex.SaveChanges();
}
Now the new Items are in the Database, but the datagridview stays the same
This is the code to display the database
frachtkostenEntities context = new frachtkostenEntities();
var item = from p in context.Artikel
select new
{
Artikelnummer = p.Artikelnummer,
Bezeichnung = p.Bezeichnung,
};
dgvDisplayDataBase.DataSource = item.ToList();
your problem is that new data has not been uploaded, you can create a function
void LoadData()
{
//code load data from db
}
Then add code to the event AddButton
void button1_Click(object sender, EventArgs e)
{
//code add new record
LoadData();
}
There is no reason to refresh, reload or invalidate when setup properly as per below.
To see immediate change when adding a new item is to setup with a BindingList and a BindingSource. Then add the new item to the BindingList.
Also consider using INotifyPropertyChanged for when a property value changes in the model.
Using the following model
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
using North.Interfaces;
namespace North.Models
{
public partial class Contacts : INotifyPropertyChanged
{
private string _firstName;
private string _lastName;
private int? _contactTypeIdentifier;
public int Id => ContactId;
public int ContactId { get; set; }
public string FirstName
{
get => _firstName;
set
{
_firstName = value;
OnPropertyChanged();
}
}
public string LastName
{
get => _lastName;
set
{
_lastName = value;
OnPropertyChanged();
}
}
public int? ContactTypeIdentifier
{
get => _contactTypeIdentifier;
set
{
_contactTypeIdentifier = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then in the form, load data to the BindingList, add button adds a new record and shows the new primary key.
namespace North.Forms
{
public partial class ContactAddForm : Form
{
private BindingList<Contacts> _bindingList;
private readonly BindingSource _bindingSource = new BindingSource();
public ContactAddForm()
{
InitializeComponent();
dataGridView1.AutoGenerateColumns = false;
Shown += OnShown;
}
private void OnShown(object sender, EventArgs e)
{
using (var context = new NorthwindContext())
{
_bindingList = new BindingList<Contacts>(context.Contacts.ToList());
_bindingSource.DataSource = _bindingList;
dataGridView1.DataSource = _bindingSource;
}
_bindingSource.MoveLast();
}
private void AddNewContactButton_Click(object sender, EventArgs e)
{
// hard coded contact
var newContact = new Contacts()
{
FirstName = "Karen",
LastName = "Payne",
ContactTypeIdentifier = 1
};
// add to list and display in DataGridView
_bindingList.Add(newContact);
// save changes
using (var context = new NorthwindContext())
{
context.Add(newContact).State = EntityState.Added;
context.SaveChanges();
}
// See new primary key
MessageBox.Show("Id " + newContact.ContactId.ToString());
}
}
}
Edit
To see values of the current row in the DataGridView
private void CurrentContactButton_Click(object sender, EventArgs e)
{
Contacts current = _bindingList[_bindingSource.Position];
MessageBox.Show($"{current.ContactId}, {current.FirstName}, {current.LastName}");
}

C# Winforms - bind textbox to array element from class

I'm currently trying to bind an array element from a class to a text box without success.
class Test{
...
string[] toto = new string[]{"element1"};
}
Test test;
void form_load()
{
test = new Test();
textBox1.DataBinding.Add("Text", test, "toto(0)");
}
(I tried as discussed here : Winforms Databinding to element in array)
But I get :
System.ArgumentException: 'Cannot bind to the property or column
Requires(0) on the DataSource. Parameter name: dataMember'
If I bind it like this:
checkBox2.DataBindings.Add(new Binding("Checked", config.Requires[0], ""));
It works but I can't implement INotifyPropertyChanged for updating the form on change perform by the code.
Does anyone have any idea?
Edit: After binding, the form should be updated when the element of the array is updated.
Quite possibly there is a better way to do this, but what you could do is create an instance of BindingSource for each element in the array, set the BindingSource.Position property, then set that as the binding for the TextBox.
Edit: Made the Data Binding 2-way... changing the value in the control updates the object, changing the value from the object changes the control.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Test
{
public class Foo
{
public Foo()
{
Items = new BindingList<string>();
}
public IList<string> Items { get; private set; }
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
_dataSource.Items.Add("Value");
_dataSource.Items.Add("Value 2");
_dataSource.Items.Add("Value 3");
var frm = new Form();
var flp = new FlowLayoutPanel
{
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.TopDown
};
for (int i = 0; i < _dataSource.Items.Count; i++)
{
var bs = new BindingSource(_dataSource, "Items");
bs.Position = i;
var tb = new TextBox();
tb.DataBindings.Add("Text", bs, "");
flp.Controls.Add(tb);
}
frm.Controls.Add(flp);
var btn = new Button()
{
Text = "Show Object's Values",
Dock = DockStyle.Bottom
};
btn.Click += btn_Click;
frm.Controls.Add(btn);
var btn2 = new Button()
{
Text = "Change Object's Values",
Dock = DockStyle.Bottom
};
btn2.Click += btn2_Click;
frm.Controls.Add(btn2);
Application.Run(frm);
}
static void btn_Click(object sender, EventArgs e)
{
MessageBox.Show(string.Join(Environment.NewLine, _dataSource.Items.ToArray()));
}
static void btn2_Click(object sender, EventArgs e)
{
var rng = new Random();
for (int i = 0; i < _dataSource.Items.Count; i++)
{
var b = new byte[8];
rng.NextBytes(b);
_dataSource.Items[i] = Convert.ToBase64String(b);
}
}
static Foo _dataSource = new Foo();
}
}
You could create a property as follows:
private string _element1;
public string Element1
{
get
{
return _element1;
}
set
{
element1 = value;
OnPropertyChanged(nameof(Element1));
}
}
Set it as: Element1 = config.Requires[0];
And then use it to set the TextBox as follows: checkBox2.DataBindings.Add(new Binding("Checked", Element1, ""));

What am I doing wrong in my custom event in Window Forms?

I'm working on a project that requires me to create items in one form, put them in a List in another and them insert them for display on a list view on a third. I have a custom event set up to add the items to their appropriate place but it doesn't work and I can't figure out why.
My UserInputForm is where the info is put in. I have a get/set method to pull the data from the fields. The declared EventHandler is in this form.
public partial class UserInputForm : Form
{
public UserInputForm()
{
InitializeComponent();
}
MainForm main;
public EventHandler AddNewItem;
public Items EachItem
{
get
{
Items newItem = new Items();
newItem.Name = nameBox.Text;
newItem.Month = monthBox.Text;
newItem.Day = dayBox.Value;
newItem.Senior = seniorCheckBox.Checked;
newItem.ImageIndex = monthBox.SelectedIndex;
return newItem;
}
set
{
nameBox.Text = value.Name;
monthBox.Text = value.Month;
dayBox.Value = value.Day;
seniorCheckBox.Checked = value.Senior;
}
}
private void AddItem (object sender, EventArgs e)
{
if (main == null)
{
main = new MainForm();
}
AddNewItem += main.AddNewItemHandler;
if (AddNewItem != null)
{
AddNewItem(this, new EventArgs());
}
EachItem = new Items();
}
}
In the Main, I have the event that adds the item to the list, calls a method to display the # of items in the list, and calls a method in the ListView Form to add it there.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
List<Items> inputItems = new List<Items>();
UserInputForm newInput;
ListView list;
public void AddNewItemHandler(object sender, EventArgs e)
{
newInput = sender as UserInputForm;
Items newItem = newInput.EachItem;
inputItems.Add(newItem);
if (list == null)
{
list = new ListView();
}
list.AddToListView(newItem);
TotalInList();
}
private void OpenInputForm(object sender, EventArgs e)
{
newInput = new UserInputForm();
if (newInput.IsDisposed == true)
{
newInput = new UserInputForm();
}
newInput.Show();
OpenInputNum();
newInput.FormClosed += InputFormClosed;
}
private void OpenListView(object sender, EventArgs e)
{
if (Application.OpenForms.OfType<ListView>().Count<ListView>() == 1)
{
Application.OpenForms.OfType<ListView>().First().Close();
displayList.Checked = false;
}
else
{
if (list == null)
{
list = new ListView();
}
if (list.IsDisposed == true)
{
list = new ListView();
}
list.Show();
list.Load += ListOpened;
}
}
private void OpenInputNum()
{
string inputNum = $"{Application.OpenForms.OfType<UserInputForm>().Count<UserInputForm>()}";
openInputNumBox.Text = inputNum;
}
private void ListOpened(object sender, EventArgs e)
{
if (list == null)
{
list = new ListView();
}
if (list.IsDisposed == true)
{
list = new ListView();
}
list.AddToNewListView(inputItems);
}
public void TotalInList()
{
string listNum = $"{inputItems.Count()}";
itemListNumBox.Text = listNum;
}
}
The ListView Form has a method that adds all items at once when its opened or a single item when its already open. It changes the item to a ListViewItem and adds it to the list.
public partial class ListView : Form
{
public ListView()
{
InitializeComponent();
}
//list for listview
List<Items> toView;
public void AddToNewListView(List<Items> toListView)
{
toView = toListView;
ListViewItem addItem = new ListViewItem();
foreach (Items item in toView)
{
addItem.Text = item.ToString();
addItem.ImageIndex = item.ImageIndex;
addItem.Tag = item;
itemListView.Items.Add(addItem);
}
}
public void AddToListView(Items newItem)
{
ListViewItem addItem = new ListViewItem();
addItem.Text = newItem.ToString();
addItem.ImageIndex = newItem.ImageIndex;
addItem.Tag = newItem;
itemListView.Items.Add(addItem);
}
}
The problems I'm having are:
Though I follow the data and though it all seems to be there when needed, my ListView will not populate and neither will the count for the number of items in the main. Is there something wrong with my code?

When collection changed in ObservableCollection add new entries to ReactiveList

I have an ObservableCollection which getting filled from a TcpClient. When new data arrives (new Items are added), I want to create new Buttons inside an ItemsControl. It works the old way (with CollectionChanged) but I don't get it work with ReactiveUI.
I'm very new to ReactiveUI, and its quite hard for me to getting started. Could you may help me by putting me on the right path or maybe by providing some sample code?
The Idea:
public class ChooseMachineViewModel : ReactiveObject
{
public ReactiveList<Button> ButtonList { get; set; }
private Dictionary<ushort, Button> addressToButton;
//This one is normaly in another class and will be filled by a TcpClient
public readonly ObservableCollection<WWSS.Message.CUStatus> ControlUnitsStatus;
public ChooseMachineViewModel()
{
//TODO: Make this Reactive!
//The ButtonList for an ItemsControl
ButtonList = new ReactiveList<Button>();
//The Dictonary for addresses -> Button
addressToButton = new Dictionary<ushort, Button>();
//The ObservableCollection filled by a TCP Server
ControlUnitsStatus.CollectionChanged += ControlUnitsStatus_CollectionChanged;
}
private void ControlUnitsStatus_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (WWSS.Message.CUStatus stat in e.NewItems)
{
TryAddButton(stat);//When new Status arrive, try to create new button
}
}
if (e.OldItems != null)
{
foreach (WWSS.Message.CUStatus stat in e.OldItems)
{
TryRemoveButton(stat);//When Status removed, try to remove the button
}
}
}
private bool TryAddButton(WWSS.Message.CUStatus status)
{
if (!addressToButton.ContainsKey(status.Address))//if the Address is already in the dictonary don't create a button
{
var but = new Button { Content = status.Address.ToString() };
addressToButton.Add(status.Address, but);
ButtonList.Add(but);
return true;
}
return false;
}
private void TryRemoveButton(WWSS.Message.CUStatus status)
{
if (addressToButton.ContainsKey(status.Address))
{
ButtonList.Remove(addressToButton[status.Address]);
addressToButton.Remove(status.Address);
}
}
}
The trick was to use CreateDerivedCollection:
public class ChooseMachineViewModel : ReactiveObject
{
public IReactiveDerivedList<Button> ButtonList { get; set; }
public ChooseMachineViewModel(ObservableCollection<CUStatus> source)
{
addressToButton = new Dictionary<ushort, Button>();
ButtonList = ControlUnitsStatus.CreateDerivedCollection(status => new Button { Content = status.Address.ToString() },
status => !ButtonList.Any(button => button.Content.ToString().Equals(status.Address.ToString())));
}
}

How to create user control for displaying collection of other user controls in WinForms?

I need to create a user control MyTypeListControl to display collection of objects of type MyType using a user controls MyTypeDisplayControl instance for each of those objects.
So that I could
add instance of MyTypeListControl to my WinForm, then
load collection of MyType and
assign it to MyTypeListControl's DataSource.
In the result it should generate and show appropriate count of MyTypeDisplayControl instances in MyTypeListControl's instance.
In case if I needed to show list of properties - equivalent would be DataGrid with specific fields from MyType assigned to specific DataGrid's columns, but I want to view each MyType item as a user control - with more power for visual representation and functionality than DataGrid provides for it's rows.
Is that even possible?
I found this SO resource how to create My collection type, but this is only small part of the problem solution...
It is quite easy (if you know how) and doesn't take so much effort as you might think in the first place (at least for a simple implementation that handles collection of less then 100 items).
So at first lets create a MyType:
public class MyType
{
public static MyType Empty = new MyType(String.Empty, DateTime.MinValue);
public MyType(string myName, DateTime myBirthday)
{
MyName = myName;
MyBirthday = myBirthday;
}
public DateTime MyBirthday { get; private set; }
public string MyName { get; private set; }
}
At next we need a MyTypeControl:
public partial class MyTypeControl : UserControl
{
private MyType _MyType;
private Label labelBirthday;
private Label labelName;
private Label labelSeparator;
public MyTypeControl()
{
InitializeComponent();
}
public event EventHandler MyTypeChanged;
public MyType MyType
{
get { return _MyType; }
set
{
if (_MyType == value)
return;
_MyType = value ?? MyType.Empty;
OnMyTypeChanged(EventArgs.Empty);
}
}
protected virtual void OnMyTypeChanged(EventArgs eventArgs)
{
UpdateVisualization();
RaiseEvent(MyTypeChanged, eventArgs);
}
protected void UpdateVisualization()
{
SuspendLayout();
labelName.Text = _MyType.MyName;
labelBirthday.Text = _MyType.MyBirthday.ToString("F");
labelBirthday.Visible = _MyType.MyBirthday != DateTime.MinValue;
ResumeLayout();
}
private void InitializeComponent()
{
labelName = new Label();
labelBirthday = new Label();
labelSeparator = new Label();
SuspendLayout();
labelName.Dock = DockStyle.Top;
labelName.Location = new Point(0, 0);
labelName.TextAlign = ContentAlignment.MiddleCenter;
labelBirthday.Dock = DockStyle.Top;
labelBirthday.TextAlign = ContentAlignment.MiddleCenter;
labelSeparator.BorderStyle = BorderStyle.Fixed3D;
labelSeparator.Dock = DockStyle.Top;
labelSeparator.Size = new Size(150, 2);
Controls.Add(labelSeparator);
Controls.Add(labelBirthday);
Controls.Add(labelName);
MinimumSize = new Size(0, 48);
Name = "MyTypeControl";
Size = new Size(150, 48);
ResumeLayout(false);
}
private void RaiseEvent(EventHandler eventHandler, EventArgs eventArgs)
{
var temp = eventHandler;
if (temp != null)
temp(this, eventArgs);
}
}
Then comes our magically list control:
public class MyTypeListControl : UserControl
{
private ObservableCollection<MyType> _Items;
public MyTypeListControl()
{
AutoScroll = true;
_Items = new ObservableCollection<MyType>();
_Items.CollectionChanged += OnItemsCollectionChanged;
}
public Collection<MyType> Items
{
get { return _Items; }
}
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateVisualization();
}
private void UpdateVisualization()
{
SuspendLayout();
Controls.Clear();
foreach (var item in _Items)
{
var control = new MyTypeControl { MyType = item, Dock = DockStyle.Top };
Controls.Add(control);
Controls.SetChildIndex(control, 0);
}
ResumeLayout();
}
}
And now simply create the list control in your form or parent control and fill it with some meaningful values:
myTypeListControl.Items.Add(new MyType("Adam", DateTime.UtcNow.Add(-TimeSpan.FromDays(365 * 40))));
myTypeListControl.Items.Add(new MyType("Eva", DateTime.UtcNow.Add(-TimeSpan.FromDays(365 * 38))));

Categories