I'd like to create an application that reads data from a database and then shows it through a UI. The user then can add/delete/update fields and save it to the DB, pretty standard, right?
I have two tables: Motors and Measures. Motors table has a lot of fields, one of them is "company". Of course, there can be several motors from the same company, so I would like to filter those companies and get only the distinct ones in a comboBox.
I'm still playing around with the language and VS, so I've made a simple version of the UI where the user can add a new motor, in fact, the user can add the company field, because I'm trying to add a new company and see if it updates automatically in the comboBox.
For this purpose, I'm using Entity Framework and this tutorial from msdn for data binding:
https://msdn.microsoft.com/en-us/data/jj682076.aspx
The problem is that when I add a new motor (with a new company), it doesn't update if I filter the distinct ones, I mean, the following code does work and automatically updates the comboBox with all the companies:
private void MainForm_Load(object sender, EventArgs e)
{
_context = new MotorsContext();
_context.Motors.Load();
this.companyBindingSource.DataSource = _context.companies.ToBindingList();
companyBindingSource.ListChanged += CompanyBindingSource_ListChanged;
}
And the following doesn't:
private void MainForm_Load(object sender, EventArgs e)
{
_context = new MotorsContext();
_context.Motors.Load();
this.companyBindingSource.DataSource = _context.Motors.Local.ToBindingList().Select(x => x.company).Distinct();
companyBindingSource.ListChanged += CompanyBindingSource_ListChanged;
}
I've created a ListChanged method to see when the software does detect that the list has, indeed, changed. In the first code it does trigger, but it doesn't in the second. Maybe the observer isn't detecting the change in the list when I add a filter?
private void CompanyBindingSource_ListChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show("List changed!");
}
And finally, the add motor button:
private void button1_Click_1(object sender, EventArgs e)
{
if (!string.IsNullOrWhiteSpace(textBox1.Text))
{
Motor m = new Motor();
m.company = textBox1.Text;
_context.Motors.Add(m);
_context.SaveChanges();
MessageBox.Show($"New motor, id: {m.motorID}");
}
}
With the first implementation, the comboBox does update and shows every company (for every motor):
Push add button -> "List changed!" popup -> "New motor: id" popup
With the filter:
Push add button -> "New motor: id" popup
In fact the motor adds, but it doesn't show until the restart of the program.
Any idea will be much appreciated. I hope I've explained myself well.
The following line in the second example break the binding:
_context.Motors.Local.ToBindingList().Select(x => x.company).Distinct();
The reason is that the result of .Select(x => x.company).Distinct() is not a BindingList<Motor>, but a simple IEnumerable<string>
Use the following replacement:
var _companies = _context.Motors.Select(x => x.company).Distinct().ToList();
this.companyBindingSource.DataSource = _companies;
This line
this.companyBindingSource.DataSource = _context.Motors.Local.ToBindingList().Select(x => x.company).Distinct();
Returns IEnumerable<T>. In your case you want it to be list, so add .ToList();
this.companyBindingSource.DataSource = _context.Motors.Local.ToBindingList().Select(x => x.company).Distinct().ToList();
Related
I am working on a .net 4.6.1 C# winforms project that has a datagridview where users can change the order of columns.
I would like to store the new order in a db table, but have trouble finding the right event for detecting when a user changed the order of the columns.
After searching here, I was pointed to the DataGridView.ColumnDisplayIndexChanged event in this thread. But that one does not solve my issue. (it only gives a solution for multiple events when you fill the datagrid view, but that is answered easily by adding the handler after setting the datasource)
That sort of works, but gets fired multiple times when a user changes the order of columns (it f.e. looks like when changing columns A,B,C,D to D,A,B,C the event gets fired 3 times (probably for A,B,D,C - A,D,B,C - D,A,B,C)
I am having a hard time finding out how I can detect if the event is the final one (since I don't want to store all these new orders, only the final one)
My questions are:
Is this event the 'best' one to use for my case?
If so, how can I detect the final ColumnDisplayIndexChanged event (D,A,B,C)?
When you reorder columns, ColumnDisplayIndexChanged will raise for all the columns which their display index has been changed. For example if you move colum A to the position after C, the event will raise for all those three columns.
There is a solution to catch the last one. DataGridViewColumn has an internal property called DisplayIndexHasChanged which is true if the event should be fired for the column. The private method which raise the event, looks into list of the columns and for each column if that property is true, first sets it to false, then raises the event. You can read internal implementations here.
You can check if there is no column having DisplayIndexHasChanged with true value, you can say it's the last event in the sequence:
private void dgv_ColumnDisplayIndexChanged(object sender, DataGridViewColumnEventArgs e)
{
var g = (DataGridView)sender;
var property = typeof(DataGridViewColumn).GetProperty("DisplayIndexHasChanged",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (g.Columns.Cast<DataGridViewColumn>().Any(x => (bool)property.GetValue(x)))
return;
else
MessageBox.Show("Changed");
}
Just keep in mind, you should disable capturing that event when you add columns:
private void f_Load(object sender, EventArgs e)
{
LoadData();
}
void LoadData()
{
dgv.ColumnDisplayIndexChanged -= dgv_ColumnDisplayIndexChanged;
dgv.DataSource = null;
var dt = new DataTable();
dt.Columns.Add("A");
dt.Columns.Add("B");
dt.Columns.Add("C");
dgv.DataSource = dt;
dgv.ColumnDisplayIndexChanged += dgv_ColumnDisplayIndexChanged;
}
My suggestion would be not to do any custom logic to find out if its the last one or something along those lines. The best approach would be to save after each event but you can debounce it.
Using a debounce approach you can cancel the old event if the new event is fired right after depending on some amount of time you wish to allow inbetween calls.
Ex: write to storage only if there is no new event after lets say 1 second or 5 seconds depending on what is accepteable for your application
Say we decide to save with a debounce of 1 second
First event occurs you trigger the action which has 1 second to execute
If another event is triggered the old action is ignored and the new action now has 1 second to execute and so on for other sequential actions
public static Action Debounce(this Action func, int milliseconds = 300)
{
var last = 0;
return arg =>
{
var current = Interlocked.Increment(ref last);
Task.Delay(milliseconds).ContinueWith(task =>
{
if (current == last) func(arg);
task.Dispose();
});
};
}
Assuming the following action below for saving your data
Action a = (arg) =>
{
save my data here
};
first assign the debouncer to your action
var debouncedWrapper = a.Debounce(1000); //1 sec debounce
Then you can use it as follows
public void datagridchangeevent(object sender, Event e)
{
debouncedWrapper()
}
This will ignore sequential calls and the aciton will be executed only if nothing is called for one second
I'm trying to load data from file to list and show that data immediately on Winforms' Datagridview. For that I've made the reading in another thread using Backgroundworker. The problem is, it only updates once and I can't get it to show more data. Not only that, when clicked, it tries to access element with -1 index, which of course doesn't exist, resulting in a crash.
Usually, from what I've seen, simply adding again same data to data source dataGridView1.DataSource = samelist; should work, but not in this case.
BackgroundWorker's work
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//lotsofCode...
while (readData != null)
{
fooLists.Add(readData);
//someCalculations...
worker.ReportProgress();
}
}
BackgroundWorker's progressChanged
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Invoke((MethodInvoker)delegate { UpdateGridView(); });
}
UpdateGridView Method
private void UpdateGridView()
{
if (fooLists.GetListById(1).calculatedList != null)
dataGridView1.DataSource = fooLists.GetListById(1).calculatedList;
}
Later on I've read some threads on stack, where one suggested using BindingSource as a "middleman", so now I have dataGridView1.DataSource = MyBindingSource; in the component initialization and tab1source.DataSource = fooLists.GetListById(1).calculatedList; instead of dataGridView1.DataSource. It certainly helped, as the list is now clickable as it should be, but still there are only few records on a list.
None of dataGridView1.Refresh(), dataGridView1.RefreshEdit() or dataGridView1.Update() helped, though made the list loading slightly fancier (probably due to the delay they introduced :) ).
I tried making some "protections" (semaphores, so the delegate isn't called again, while working; try-catches, though no exceptions are thrown there; data clearing before re-writing...) but the "better version" worked as poor as this one and it only darkened the code.
Am I missing a way to update the Datagridview control? Thanks in advance.
Although you didn't write it, but I think the reason that the items that you add to your dataSource are added to a collection that does not implement interface IBindingList. You probably use a simple list to hold your read data.
If your 'DataSourceimplements this interface, then after adding an item to your collection an event is raised. The class that holds theDataSource, whether it is aDataGridViewor aBindingSource` get notified about the changes in the list and update their contents accordingly.
Your solution would be to store your elements in an object of class System.ComponentModel.BindingList<T>.
Suppose the items you want to show are of class MyReadData
class MyForm : Form
{
public MyForm()
{
InitializeComponents();
this.myReadItems = new BindingList<MyReadData>();
this.MyBindingSource.DataSource = this.myReadItems;
// if not already done in InitializeComponents
this.MyDataGridView.DataSource = this.MyBindingSource;
}
private readonly BindingList<MyReadData> myReadItems;
// whenever needed, start the BackGroundWorker.
private void OnButtonReadFile_Click(object send, EventArgs e)
{
// create and start the backgroundworker
BackGroundWorkdr worker = ...
MyBackGroundWorkerParams params = ...
worker.RunWorkerAsync(params);
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
// I am certain the sender is my BackGroundWorker:
BackgroundWorker worker = (BackGroundWorker)sender;
MyBackGroundWorkerParams params = (MyBackGroundWorkerParams)e.Argument;
// do some work using the params
while (readData != null)
{
// some data read.
// dont't add the data to the list, just report the data that must been added to the list:
// someCalculations...
int percentProgress = ...
MyReadData dataToAddToGrid = ...
worker.ReportProgress(percentProgress, dataToAddToGrid);
}
private void bw_progressChanged(object sender, ProgressChangedEventArgs e)
{
// no need to call invoke, this is already the context of your forms thread
Debug.Assert(!This.InvokeReguired);
MyReadData dataToAdddToGrid = (MyReadData)e.UserState;
this.myReadItems.Add(dataToAddToGrid);
}
}
The main difference is that you should not let your BackgroundWorker to add data to the list of displayed data. The task of the BackGroundWorker is to read the data and to report to everyone who is interested what data has been read.
As it is the task of MyForm to display the read data, let MyForm decide which read data to display and in what format. This enhances reusage of both MyForm and MyBackGroundWorker: MyForm could display that that has been fetched in a different way, and MyBackGroundWorker could be used to inform others than MyForm to notify about read data.
Furthermore the display context of the progress changed event handler is the context of 'MyForm`, so an invoke is not needed.
You could also assign the IBindingList directly to the DataGridView, so without the use of a BindingSource. The only reason to keep a BindingSource is if you want access to the Current item, or if you want the freedom to fill your DataGridView with other items than the contents of your BindingList.
Finally: the most important part of the solution was that the items were added to an IBindingList.
System.Components.BindingList<T> is a class with limited functionality. If you want to order the rows in your DataGridView, or only show items that match some predicate, or combine items from several sources into one DataGridView, consider using Equin.ApplicationFramework.BindingListView
using Equin.ApplicationFramework;
public MyForm()
{
InitializeComponents();
this.myReadItems = new BindingListView<MyReadData>(this.components);
this.MyBindingSource.DataSource = this.myReadItems;
this.MyDataGridView.DataSource = this.MyBindingSource;
}
private readonly BindingListView<MyReadData> myReadItems;
private void bw_progressChanged(object sender, ProgressChangedEventArgs e)
{
MyReadData dataToAdddToGrid = (MyReadData)e.UserState;
this.myReadItems.Add(dataToAddToGrid);
// finished updating the list, DataGridView can be updated:
this.myReadItems.Refresh();
// this Refresh function allows you to change several items in the list
// without unnecessary intermediate updates of your BindingSource and DataGridView
}
Presto, that is all: free sorting or your columns by clicking on the column header. Consider examining their example project to see how filtering works and how to use several sources.
// Show only Brummies
this.myReadData.ApplyFilter(person => person.Address.City == "Birmingham");
// Remove the filter, show everyone again
this.myReadData.RemoveFilter();
I am currently working on Windows Store App in c#.
Now,
I am having a list box 'Listbox1' which gets its items on a button click event from a text box 'tasks', and have selected Items delete property on other button click event.
private void add_Click(object sender, RoutedEventArgs e)
{
string t;
t = tasks.Text;
if (t != "")
{
Listbox1.Items.Add(t);
}
else
{
var a = new MessageDialog("Please Enter the Task First");
a.Commands.Add(new UICommand("Ok"));
a.ShowAsync();
}
tasks.Text = "";
}
private void del_Click(object sender, RoutedEventArgs e)
{
for (int p = 0; p < Listbox1.SelectedItems.Count; p++)
{
Listbox1.Items.Remove(Listbox1.SelectedItems[p].ToString());
p--;
}
}
Now I want to save this list into local application storage, after user complete the changes (on a button click event perhaps).
And also to send all Listbox Items to another page(s).
I am not much a coder, I design things.
Please guide me by sample or reference.
Thank you in advance :)
If you have already stored the data to local storage, you could just read it in the OnNavigatedTo override of the other page. Otherwise, use the navigation parameter: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/8cb42356-82bc-4d77-9bbc-ae186990cfd5/passing-parameters-during-navigation-in-windows-8
Edit: I am not sure whether you also need some information about local storage. This is easy: Windows.Storage.ApplicationData.Current.LocalSettings has a property called Values, which is a Dictionary you can write your settings to. Have a look at http://msdn.microsoft.com/en-us/library/windows/apps/hh700361.aspx
Edit: Try something like this code to store your list.
// Try to get the old stuff from local storage.
object oldData = null;
ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
bool isFound = settings.Values.TryGetValue("List", out oldData);
// Save a list to local storage. (You cannot store the list directly, because it is not
// serialisable, so we use the detours via an array.)
List<string> newData = new List<string>(new string[] { "test", "blah", "blubb" });
settings.Values["List"] = newData.ToArray();
// Test whether the saved list contains the expected data.
Debug.Assert(!isFound || Enumerable.SequenceEqual((string[]) oldData, newData));
Note, this is only demo code for testing - it does not make real sense...
Edit: One advice: Do not persist the list in your click handlers as this will become extremely slow as the list grows. I would load and save the list in the Navigation handlers, i.e. add something like
protected override void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
if (this.ListBox1.ItemsSource == null) {
object list;
if (ApplicationData.Current.LocalSettings.Values.TryGetValue("List", out list)) {
this.ListBox1.ItemsSource = new List<string>((string[]) list);
} else {
this.ListBox1.ItemsSource = new List<string>();
}
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e) {
if (this.ListBox1.ItemsSource != null) {
ApplicationData.Current.LocalSettings.Values["List"] = this.ListBox1.ItemsSource.ToArray();
}
base.OnNavigatedFrom(e);
}
Here is very nice simple example on SQLite DataBase Use in winRT app Development. Look at it and you will know how you can store your Data on the Local Machine. I learned Basic code from this example.
http://blogs.msdn.com/b/robertgreen/archive/2012/11/13/using-sqlite-in-windows-store-apps.aspx
Now, for ease of navigation let me suggest you a flow for this portion of your app.
take one ObservableCollection<> of string and store values of
that textBox into this ObservationCollection with onClick() and then
refer that ObservableCollection<String> to the ItemsList of the
listBox.
now at the time you need to send your Data to the next page, make one parameterised constructor of next page and pass that ObservableCollection<String> as it's parameter.
Now you can access those Data in your constructor and can use as however you want.
Hope this will help..
I'm beginner with C# and wp7 platform and I have some problem with good idea to get request from web service.
I made webservice in PHP (nusoap - WSDL) and everything is working fine in "normal" using.
Now I have ObservableCollection saved in IsolatedStorage with I load when Page is open (List of watched stacks exchange). Then I want to refresh data for every item from web service.
I don't know whether this is a good idea.
Code:
private GPWWebservicePortTypeClient client = new GPWWebservicePortTypeClient();
private ObservableCollection<WebServiceClass.ItemGetValues> StoredStock =
new ObservableCollection<WebServiceClass.ItemGetValues>();
public const string _fileName = "listaObserwowanych.xml";
public Page()
{
InitializeComponent();
DataContext = App.ViewModel;
this.Loaded += new RoutedEventHandler(Page_Loaded);
client.GetLastValueCompleted +=
new EventHandler<GetLastValueCompletedEventArgs>(client_GetLastValueCompleted);
foreach (var itemGetValuese in App.ViewModel.Items)
{
client.GetLastValueAsync(itemGetValuese.name);
}
var o =
Observable.FromEvent<GetLastValueCompletedEventArgs(client,"GetLastValueCompleted")
.Subscribe(setList);
}
void client_GetLastValueCompleted(object sender, GetLastValueCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(Convert.ToString(e.Error));
}
else
{
ObservableCollection<WebServiceClass.ItemGetValues> ListValues =
(ObservableCollection<WebServiceClass.ItemGetValues>)
JsonConvert.DeserializeObject(e.Result,
typeof(ObservableCollection<WebServiceClass.ItemGetValues>));
StoredStock.Add(ListValues[0]);
}
}
private void setList(IEvent<GetLastValueCompletedEventArgs> ex)
{
List.ItemsSource = StoredStock;
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
App.ViewModel.LoadData();
List.ItemsSource = App.ViewModel.Items;
}
Like u see I use RX to call method client_GetLastValueCompleted add store result to auxiliary variable (StoredStock). Then refresh List in setList method, but that method is client_GetLastValueCompleted what is not soo good idea, becouse I need to run that method only when all of runned GetLastValueAsync in foreach is completed.
Second problem: becouse of async web service method StoredStock sometime have different order than App.ViewModel.Items .
Any good idea how to do that in right way?
Best regards,
Lukas
You're really mixing up a number of ways to call web services and Rx. You really need to decide on a single way and stick to it.
If you're going to use Rx, then you'll have something like this:
public Page()
{
InitializeComponent();
DataContext = App.ViewModel;
this.Loaded += new RoutedEventHandler(Page_Loaded);
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
App.ViewModel.LoadData();
var storedStock =
new ObservableCollection<WebServiceClass.ItemGetValues>();
List.ItemsSource = storedStock;
var values =
Observable.Using<WebServiceClass.ItemGetValues, GPWWebservicePortTypeClient>
(() => new GPWWebservicePortTypeClient(), ws =>
{
var clientGetLastValue = Observable
.FromAsyncPattern<string, GetLastValueResponse>
(ws.BeginGetLastValue, ws.EndGetLastValue);
Func<string, WebServiceClass.ItemGetValues> deserializeFirst = r =>
((List<WebServiceClass.ItemGetValues>)JsonConvert
.DeserializeObject(r,
typeof(List<WebServiceClass.ItemGetValues>)))
.First();
return
from item in App.ViewModel.Items
from e in clientGetLastValue(item)
select deserializeFirst(e.Result);
});
values.Subscribe(storedStock.Add);
}
You'll have to get the right method call names for your web service client, but the code should roughly be right. Let me know how you go.
I corrected the code above. Should have returned the query inside the Using call rather than assign it to values.
I corrected the call to FromAsyncPattern to use the correct method names and return type from the actual web service reference class sent via email.
It should look like this:
Observable.FromAsyncPattern<string, GetLastValueResponse>
(ws.BeginGetLastValue, ws.EndGetLastValue);
If you're a beginner with C#, try to avoid RX for the time being. It is a cool technology, but if you use it without clear understanding of what is going on, it will bring more problems than solve.
Use a simple event, and when each async item arrives, locate and update the correspondent one in the stored list.
I have a Silverlight application that displays a map, and my intention is, when I mouse over a specific location in the map, the information about this location gets displayed somehow in the app. What I've done so far is to link the silverlight app to a webservice that retrieves this information for me, but now I'm stuck, and don't know how to proceed. I was following this tutorial., but when the tutorial wants to retrieve a list, I want to retrieve a single object. I was trying to use the datagrid, but I think it was not designed to perform what I want. I need some enlightment to tell me how to proceed.
Well ... I'll edit the code to show what problem I'm having. My code behind have this two methods:
private void MouseOverHarbor(object sender, RoutedEventArgs e)
{
Ellipse thisPath = (Ellipse)sender;
thisPath.Stroke = mySolidColorBrush;
DataRetrieverReference.Service1Client webService = new DataRetrieverReference.Service1Client();
webService.GetDataCompleted += new EventHandler<DataRetrieverReference.GetDataCompletedEventArgs>(webService_GetDataCompleted);
webService.GetDataAsync((int)thisPath.DataContext);
}
void webService_GetDataCompleted(object sender, DataRetrieverReference.GetDataCompletedEventArgs e)
{
NameField.Text = "Works";//No, it doesnt!
}
What I can see is that the event handler is never reached, but I don't know why. I just used the same code the tutorials taught, but I didn't achieve my goal yet. Am I missing something?
Remove the List< > part from List<Customer> in your calls and keep only the Customer part.
Change your queries from
var matchingCustomers = from cust in db.Customers
where cust.LastName.StartsWith(lastName)
select cust;
return matchingCustomers.ToList();
to
var matchingCustomers = from cust in db.Customers
where cust.LastName.StartsWith(lastName)
select cust;
return matchingCustomers.FirstOrDefault();