How to avoid the overwriting in the DataBinding? - c#

im new at programming and need help here... I want to create a Binding with a Combobox Item.
But the DataBinding is not adding a new DataBind, it overwrites the old one because of the loop. So i want if you select a "Profilname" in the Combobox that the "Path" will be displayed.
But so far, just the last loaded .txt file will be displayed because of the overwrite.
Here is now my question: How to avoid the overwrite of the DataBind in the (foreach)-loop?
For information: There is a folder which contains many .txt-files, which are all called: "profile.txt". The Programm search for all the files with a loop and then search in the files with another loop a line, which contains the word "profile_name". And then the Name has to be displayed in the ComboBox and the Path has to be binded to the "Item"/"Text" in the ComboBox.
I hope this is understandable and sorry if my code is confusing or not very strong written, im learning...
foreach (string profiletxt in Directory.EnumerateFiles(profiledirectory, "profile.txt", SearchOption.AllDirectories))
{
foreach (string line in System.IO.File.ReadAllLines(profiletxt))
{
if (line.Contains("profile_name"))
{
string remLine = line.Remove(0, 15);
string dLine = remLine.Replace("\"", "");
// dataBinding
var listProfiles = new List<Profile>() {
new Profile() {Path = profiletxt, Name = dLine,},
};
materialComboBox1.DataSource = listProfiles;
materialComboBox1.DisplayMember = "Name";
materialComboBox1.ValueMember = "Path";
}
}
if (materialComboBox1.SelectedIndex == -1)
{
MessageBox.Show("Error, couldn't find Profiles");
}
}
public class Profile
{
public string Path { get; set; }
public string Name { get; set; }
}

a ComboBox uses its ItemSource containing the available items. In your inner foreach loop you declare a new profile list for every find of profile item:
var listProfiles = new List<Profile>() {
new Profile() {Path = profiletxt, Name = dLine,},
};
materialComboBox1.DataSource = listProfiles;
Instead, you'd probably like to create a new Profile list before the first foreach loop
var listProfiles = new List<Profile>();
and in the inner loop, add your new finding to the list
listProfiles.Add(new Profile() {Path = profiletxt, Name = dLine});
Then, after the outer loop, you may assign the new ItemSource only once.
There are other newby design flaws in your code:
there should be no need to set DisplayMember and ValueMember in the .xaml.cs "code behind". Rather it belongs into the xaml code itself as these are static.
As a more general advise, consider not doing any kind of "business rules stuff" or data holding in your code behind. Rather you like to separate your UI ("View") from your data ("Model") while a "ViewModel" separates these two and implements the business rules. There are tons of good introductions on this MVVM programming pattern out there.

Related

Making Changes to List Causes DataGridView to show blank

I have a project where a user can add new products or modify existing ones. Products can have parts associated with them. I created a list in the class where my product constructor lives to hold parts for each product.
I'm trying to figure out how to set up that if a user makes changes to the parts that are associated with a product but hits the cancel button then the list reverts to the original list. If they hit the save button after making edits to the products parts list then the updated list is saved and when they open the product again then the updated list displays.
All the code in the file I have is quite a bit but the parts I thought would be most helpful for what I have are...
Filling in the product text boxes with the information on the product. Populating the datagridview CurrentPartsDataGrid with the tempParts list that is a copy of the product.Parts list. Or creating a new empty list if it's a new product that is created.
if (product != null)
{
ProductIdText.Text = product.ID.ToString();
ProductNameText.Text = product.Name.ToString();
InvText.Text = product.QOH.ToString();
PriceText.Text = product.Price.ToString();
InvMinText.Text = product.Min.ToString();
InvMaxText.Text = product.Max.ToString();
tempParts = new BindingList<Part>(product.Parts);
}
else
{
product = new Product();
tempParts = new BindingList<Part>();
}
CurrentPartsDataGrid.DataSource = tempParts;
In the save/cancel button click event methods I have tried doing a for loop or for each loop. I clear the list and then try to repopulate.
Code in save
product.Parts.Clear();
foreach (Part part in tempParts)
{
product.Parts.Add(part);
}
Code in Cancel
tempParts.Clear();
foreach (Part part in product.Parts)
{
tempParts.Add(part);
}
If there is anything else that would be helpful, let me know. I'm new to posting here so don't want to overload the post but also don't want to not provide enough.
Any help on how to fix this would be awesome.
Thanks!
I ended up not needing to do any of the EndEdit() stuff.
In my (if product != null)
I added
tempList = new BindingList<Part>();
for (int i = 0; i < product.Parts.Count; i++)
{
tempList.Add(product.Parts[i]);
}
This created a new templist that when the user selected cancel I set product.Parts = tempList which resolved the issue.

Saving a list between applications in the .NET property settings

I have a simple list, like this:
fruitList = new FruitList();
Apple fruit1 = new Apple("red", "small");
Banana fruit2 = new Banana("yellow", "big");
fruitList.AddFruit(fruit1);
fruitList.AddFruit(fruit2);
My program displays this in a Textbox:
textbox.Text = fruitList.DescribeCurrentFruit()
+
public string DescribeCurrentFruit()
{
string description;
if (fruitStock.Count > 0)
{
description = fruitStock[fruitCurrentlyDisplayed].Description();
}
else
{
description = "No Fruits in stock";
}
return description;
}
Currently, the List's two current objects/items (fruit1, fruit2) are automatically loaded as they are a part of my Window Form's load_event. However, if they're not a part of the load_event, or if I want to add more items/objects to the list at runtime, then permanently save them, how can I do so?
Well, I can do so by saving items in the project's property settings. (Serialization is an alternative option, but far too complex for me, and I want the simplest solution.) How do I go about this? Is there any sample code? I understand I first need to add items into my properties, but struggle even at this step.

ListBoxView populated using Linq based on TimePicker Value

Using WPF C#
Have a TimePicker from Xceed.WPF.Toolkit called TimePicker, want to be able to filter the files that are loaded to a listbox based on the last write time of a file
Question: I would like to limit the files listed based on the logTime variable using Linq and where the files have to match the logTime
Code for population of list box currently
private void LoadLogsNoDate(string ldate, string ext)
{
string[] logs = Directory.GetFiles(logPath + ldate, ext);
InitializeComponent();
logList = new ObservableCollection<String>();
logList.Clear();
lbLogs.ItemsSource = logList;
foreach (string logName in logs)
{
string s = logName.Substring(logName.IndexOf(ldate) + ldate.Length + 1);
int extPos = s.LastIndexOf("."); // <- finds the extension
s = s.Substring(0, extPos); // <- removes the extension
s = s.ToUpper(); // <- converts to uppercase
logList.Add(s); // <- adds the items it finds
}
DataContext = this;
}
Need to set the TimePicker value to a logTime variable then use the logTime to filter the list of items that are displayed
Successfully have used this code to get the LastWriteAccess time just need some assistance putting it all together properly
public static void Times(string sFile)
{
FileInfo info = new FileInfo(sFile);
DateTime time = info.LastWriteTime;
string s = time.ToString("HH:mm tt");
Console.WriteLine("Last Access: " + s);
}
First off, you will want to associate the time with the string representing the log file. Instead of an ObservableCollection you'll have an ObservableCollection. Log would be defined as:
public class Log
{
String LogName;
DateTime LogWriteTime;
}
Creating the collection would call your other function (modified to return the read time):
foreach (string logName in logs)
{
string s = logName.Substring(logName.IndexOf(ldate) + ldate.Length + 1);
int extPos = s.LastIndexOf("."); // <- finds the extension
s = s.Substring(0, extPos); // <- removes the extension
s = s.ToUpper(); // <- converts to uppercase
Log newItem = new Log();
newItem.LogName = s;
newItem.LogWriteTime = GetFileAccessTime(s)
logList.Add(s); // <- adds the items it finds
}
public DateTime GetFileAccessTime(string sFile)
{
FileInfo info = new FileInfo(sFile);
return info.LastWriteTime;
}
Now that we have our time stored off, I'm going to assume your TimePicker's SelectedValue property is bound to FilterTime. There are two ways to approach the problem of filtering:
Bind your view to a separate IEnumerable FilteredLogs and do the following in the setter of the FilterTime variable:
FilterdLogs = logList.Where(l => l.LogWriteTime >= FilterTime);
Use a CollectionViewSource. This method is awesome! First, create a CollectionViewSource property called LogsSource. Change your XAML to bind to this instead of the old ObservableCollection.
<ListBox ItemsSource="{Binding FilteredLogs.View}"/>
Now, in whatever you have for an Init function (constructor, whatever calls LoadLogsNoData, etc.) write:
FilteredLogs = new CollectionViewSource();
FilteredLogs.Source = logList;
FilteredLogs.Filter += CheckAccessTime;
This sets up a new CollectionViewSource that points to your logList collection, and uses the CheckAccessTime function to determine if a particular log entry should be included in the "View" property (which you previously bound to).
The CheckAccessTime function will look like:
private void CheckAccessTime(object sender, FilterEventArgs e)
{
Log logEntry = e.Item as Log;
if (logEntry != null)
{
if (logEntry.LogWriteTime >= FilterTime)
{
e.Accepted = true;
}
else
{
e.Accepted = false;
}
}
}
Finally, you need to refresh the filter anytime the selection changes. So, in the setter of FilterTime write:
FilteredLogs.View.Refresh();
The second option is, in my opinion, a much cleaner way of accomplishing the task, though it may be a bit more confusing at first. Let me me know if I can clarify anything!
This blog post was greatly helpful in researching the second method: http://uicraftsman.com/blog/2010/10/27/filtering-data-using-collectionviewsource/
MSDN for the filter event: MSDN

Create/Increment ObservableCollection On Button Click

I am looking to see if it is possible to be able to increment an ObservableCollectionevery time a button is clicked?
ObservableCollection<string> _title = new ObservableCollection<string>();
public ObservableCollection<string> Title
{
get { return _title; }
set
{
_title = value;
OnPropertyChanged(() => Title);
}
}
As shown from the C# code above, I have an ObservableCollection for a Title. Currently, when I add a new title, every title goes into the same collection. However, what I am aiming to achieve is; every time the "Add Title" button is pressed, a new title is added and a new ObservableCollection is created. Is this possible, and how can it be done?
EDIT1
At the moment I dynamically create Textboxes and then add whatever string I want to that Textbox. From there I save the Stackpanel, named 'Content', into a .txt file. In this file it will hold the Textboxs that have been created. (It does not save the string into that file due to the textbox being binded). I then thought the strings would be saved into a list, and when I load the Stackpanel back up from the .txt file the string would get added back to the Textbox.
EDIT2
I have changed a bit of code:
public ViewModel()
{
this.AddTitleCommand = new RelayCommand(new Action<object>((o) => OnAddTitle()));
}
private void OnAddTitle()
{
NewTitle += titleName;
}
When doing this it is not adding my string as a word it is separating the letters in my string in separate titles.
If I understand you correctly, each time your Button is pressed, you want to add a new string into a new collection. You then said that you will add other values into each collection... this sounds like you're trying to fulfil your requirements in the wrong way, but you didn't tell us what those were, so we can't help you with that. Here's how you can add a new collection each time:
private string newTitle = string.Empty;
private ObservableCollection<ObservableCollection<string>> collections = new
ObservableCollection<ObservableCollection<string>>();
public ObservableCollection<ObservableCollection<string>> Collections
{
get { return collections; }
set { collections = value; OnPropertyChanged(() => Collections); }
}
public string NewTitle
{
get { return newTitle; }
set { newTitle = value; OnPropertyChanged(() => NewTitle); }
}
public void AddCollection()
{
ObservableCollection<string> collection = new ObservableCollection<string>();
collection.Add(NewTitle);
Collections.Add(collection);
}
The NewTitle property could be data bound to a TextBox in the UI allowing users to enter the new values and when the Button is pressed, the AddCollection method would add it into a new collection and then add that into the Collections collection.
I still think that this is not a good idea though.
UPDATE >>>
Please stop what you're doing... programs are not written like that. We save data, the strings, not UI elements. There is absolutely no point in saving the UI elements along with all their extra property values that you have no interest in. Whatever method you have of displaying the strings in the TextBoxes can be reused each time the data is loaded.
ObservableCollection provides a constructor that takes an IEnumerable so use it to create a new instance with the same titles and add your new title afterward :
ObservableCollection<string> newCollection = new ObservableCollection<string>(Title);
newCollection.Add(theNewTitle)
Title = newCollection;

Listview Add Items not appearing C#

I'm working on a small program as part of my A Level Computing course that is designed to track orders. It is written in C# using the Windows Forms.
I am having an issue where I enter all the information for a new order and then press OK and it should update the ListView with the information. I have my ListView in Detail view with 4 columns but nothing ever gets added to the ListView. The section of code that should add the items to the ListView is being executed and is not throwing any errors or causing the program to crash but nothing is being added. Its weird because I am using the exact same method that I used in my little prototype mock up but for some reason now it is not working.
All the things I've found on here or on the internet seem to suggest its an issue with the View mode of the ListView and I've tried modifying this property to no avail.
Any ideas why this section of code is refusing to add anything to the ListView?
//Create an array to store the data to be added to the listbox
string[] orderDetails = { Convert.ToString(id + 1), rNameBox.Text, dateBox.Value.ToString(), orderBox.Text };
//DEBUGGING
Console.WriteLine(orderDetails[0]);
Console.WriteLine(orderDetails[1]);
Console.WriteLine(orderDetails[2]);
Console.WriteLine(orderDetails[3]);
//END DEBUGGING
//Add the order info to the ListView item on the main form
var listViewItem = new ListViewItem(orderDetails);
ths.listView1.Items.Add(listViewItem);
If you need any more information just say. Apologies if this is in the wrong format or something this is my first time here.
Your problem is that your ListViewItem contains a string array and it has no useful way of displaying it.
What you should be doing (there are a number of ways of doing this, but here's one) is creating a class, OrderDetail, with an Id, a Name, a Date, and so on. Give it a ToString() method (public override string ToString()) which returns what you want to display, e.g.:
public override string ToString()
{
return this.Name;
}
Create an instance of OrderDetail and set its properties. Create ListViewItem giving it the OrderDetail instance and add to the ListView. Repeat for as many OrderDetail instances you want.
Cheers -
Added: code which works:
int id = 12;
string rNameBoxText = "rName";
DateTime dateBoxValue = DateTime.Now;
string orderBoxText = "order";
string[] orderDetails = { Convert.ToString(id + 1), rNameBoxText, dateBoxValue.ToString(), orderBoxText };
//DEBUGGING
Console.WriteLine(orderDetails[0]);
Console.WriteLine(orderDetails[1]);
Console.WriteLine(orderDetails[2]);
Console.WriteLine(orderDetails[3]);
//END DEBUGGING
this.listView1.Columns.Clear();
this.listView1.Columns.Add("Id");
this.listView1.Columns.Add("rName");
this.listView1.Columns.Add("Date");
this.listView1.Columns.Add("Order");
this.listView1.View = View.Details;
//Add the order info to the ListView item on the main form
var listViewItem = new ListViewItem(orderDetails);
this.listView1.Items.Add(listViewItem);

Categories