ListBoxView populated using Linq based on TimePicker Value - c#

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

Related

How to avoid the overwriting in the DataBinding?

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.

How do I get data from multiple XML files in Windows Phone

I have an initial XML file stored in my phone which is accessed to select a number of elements. Each element has a corresponding online XML file which I need to access to get more information.
string name, photo;
foreach (int num in combi)
{
no = xElem.Descendants("employee").ElementAt(num).Descendants("no").First().Value;
WebClient wc = new WebClient();
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler
(Info_DownloadStringCompleted);
wc.DownloadStringAsync(new Uri
("http://example.com/" + name));
list.Add(new Person(no, name, photo);
}
void Info_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null) return;
XElement xml = XElement.Parse(e.Result);
name = xml.Element("name").Value;
photo = xml.Element("photo").Value;
}
However, it seems that the list.Add goes first before the XML is downloaded resulting into a list with empty values for name and photo. I confirmed this by placing MessageBox in both the foreach loop and Info_DownloadStringCompleted. Is there a better way of doing this?
To add to #MikkoVitala's answer, better for you to use ObservableCollection for the list.
ObservableCollection has built-in mechanism to notify UI to refresh whenever item added to or removed from collection. So it doesn't matter when you bind list to ListBox, the ListBox will always display up to date member of list :
string name, photo;
ObservableCollection<Person> list = new ObservableCollection<Person>();
foreach (int num in combi)
{
.....
}
MyListBox.ItemsSource = list;
Problem with your solution is that in your iteration over combi you'll try to use fields name and photo which are only assigned to in your eventhandler Info_DownloadStringCompleted.
Since DownloadStringAsync is, well... asynchronous, you'll exit foreach before DownloadStringCompleted event is raised, your eventhandler called and ultimately your fields been assigned to.
You can correct this by moving your list-add-logic to be executed after event has been raised.
....
wc.DownloadStringCompleted += (sender, args) =>
{
// Code from your Info_DownloadStringCompleted event handler
if (args.Error != null)
return;
XElement xml = XElement.Parse(args.Result);
name = xml.Element("name").Value;
photo = xml.Element("photo").Value;
// Now your fields are assigned and you can do-what-ever-with-'em
list.Add(new Person(no, name, photo);
};
....
If you like you can use extension methods to utilize async/await keywords and make it simpler and easier to read and understand. See SO answer here https://stackoverflow.com/a/13174270/1061668
Then above becomes (remember to mark method async)
....
// Note that exception is not handled in this example....
xml = await wc.DownloadStringTask(new Uri("http://example.com/" + name));
name = xml.Element("name").Value;
photo = xml.Element("photo").Value;
list.Add(new Person(no, name, photo);
....

Win 8.1 SearchBox SuggestionsRequested

I got an userControl that contains a searchBox.
This UserControl is inside another one.
I got a strange behavior while i'm searching, because the suggestionCollection works in a strange way.
Example :
in the searchBox i write something all works perfectly, if i choose the item it also works.
But if i try to use backspace (after the choose) i got no suggestion.
I cannot understand why it doesn't work.
That's the code
//var deferral = args.Request.GetDeferral(); //it seems to not influence the behavior
var suggestionCollection = args.Request.SearchSuggestionCollection;
try
{
TransporterExt tr_search = new TransporterExt();
//queryText is a string inserted in the searchBox
if (string.IsNullOrEmpty(queryText)) return;
tr_search.name = queryText;
suggested.Clear(); //that's a collection..
//just a search that return a collection of objects TransporterExt
querySuggestions = await TransporterService.Search_StartsWith(tr_search);
if (querySuggestions.Count > 0)
{
int i = 0;
foreach (TransporterExt tr in querySuggestions)
{
string name = tr.name;
string detail = tr.trId.ToString();
string tag = i.ToString();
string imageAlternate = "imgDesc";
suggestionCollection.AppendResultSuggestion(name, detail, tag, imgRef, imageAlternate);
this.suggested.Add(tr);
i++;
}
}
}
catch (System.ArgumentException exc)
{
//Ignore any exceptions that occur trying to find search suggestions.
Debug.WriteLine(exc.Message);
Debug.WriteLine(exc.StackTrace);
}
//deferralComplete(); //it seems to not influence the behavior
The problem is that: all the variables have the right value, but the suggestion panel appears only if i make a particular search: it appears when i change the first letter of search, or after an wrong seach
What appends when i make a search
What appends if i use the backspace, and i what i want to fix
As i said, all works perfectly, after the "backspace" action the suggestionCollection got the right value...but the panel is missing.
Could someone help me?
You can use SearchBox and SuggestionRequested event to fire the event when type on the SearchBox. I will show an Example
<SearchBox x:Name="SearchBoxSuggestions" SuggestionsRequested="SearchBoxEventsSuggestionsRequested"/>
and write the SearchBoxEventsSuggestionsRequested handler in the code behind
private void SearchBoxEventsSuggestionsRequested(object sender, SearchBoxSuggestionsRequestedEventArgs e)
{
string queryText = e.QueryText;
if (!string.IsNullOrEmpty(queryText))
{
Windows.ApplicationModel.Search.SearchSuggestionCollection suggestionCollection = e.Request.SearchSuggestionCollection;
foreach (string suggestion in SuggestionList)
{
if (suggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase))
{
suggestionCollection.AppendQuerySuggestion(suggestion);
}
}
}
}
You can add the keyword to SuggestioList, and it will show in the dropdown when you type on the Searchbox.
Create the SuggestionList
public List<string> SuggestionList { get; set; }
initialize the list
SuggestionList = new List<string>();
and add keywords to the list
SuggestionList.Add("suggestion1");
SuggestionList.Add("suggestion2");
SuggestionList.Add("suggestion3");
SuggestionList.Add("suggestion4");
SuggestionList.Add("Fruits");
Thanks.

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;

How to use winform listbox to list declared objects in C#?

I am trying to make a listbox in a winform to use a list of declared objects as the content source. Choosing an object should list its properties in a nearby text box that reads from the properties of that object. An object for the list looks something like this:
public Form1()
{
Element gold = new Element();
gold.Property = "Soft";
gold.Metal = true;
gold.Name = "Gold";
InitializeComponent();
}
I was told that putting this in my main form was the way to go with this. What I have attempted so far is giving a name string that the listbox will use to name the object that the user will select, and the other two properties (gold.Property = "Soft"; and gold.Metal = true; are meant to go in the nearby textbox when the item is selected in the listbox). I don't really know how to do this, so any sort of help for this would be appreciated. At the base, just knowing how to get the listbox to find the object I made for it and then list it, would be great.
Also, yes, this is for an assignment. So the things I have outlined need to be done in that way...there is more to the assignment itself, but where I am stuck is here.
Use List<Element> elements to store your element,
then do a loop on each element and add its name to the listbox.
Add event handler to your listbox selected index changed,
This code should do it. (Remember to check whether the selected index is -1 or not)
txtName.Text = elements[listbox.SelectedIndex].Name;
txtProperty.Text = elements[listbox.SelectedIndex].Property;
Without knowing more of your requirements, I can only guess that the assignment wants you to directly add instances of Element() to your ListBox. You can override ToString() in your Element() class to control how the ListBox will display those instances. Return the Name() property will work quite nicely. Wire up the SelectedIndexChanged() event of the ListBox and cast SelectedItem() back to Element() so you can extract the other two values. This might look something like:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
listBox1.SelectedIndexChanged += new EventHandler(listBox1_SelectedIndexChanged);
Element element = new Element();
element.Property = "Soft";
element.Metal = true;
element.Name = "Gold";
listBox1.Items.Add(element);
element = new Element();
element.Property = "Indestructible";
element.Metal = true;
element.Name = "Adamantium";
listBox1.Items.Add(element);
element = new Element();
element.Property = "Liquid";
element.Metal = true;
element.Name = "Mercury";
listBox1.Items.Add(element);
element = new Element();
element.Property = "Fluffy";
element.Metal = false;
element.Name = "Kitten";
listBox1.Items.Add(element);
}
void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listBox1.SelectedIndex != -1)
{
Element element = (Element)listBox1.SelectedItem;
label1.Text = "Property: " + element.Property;
label2.Text = "Metal: " + element.Metal.ToString();
}
}
}
public class Element
{
public string Property;
public bool Metal;
public string Name;
public override string ToString()
{
return Name;
}
}

Categories