Segmentation and displaying content from dictionary - c#

Through my initial program code, I have been able to generate a dictionary of {string, int} fo some some length (mostly 3).
In my application, I have to display the strings separated by a semi-colon, and on hovering over a certain string, it should display the corresponding int of the string as something sort of a tooltip
My idea of doing this was to have the labels (equal to number of strings in dictionary) being displayed, content of each label being picked up from the dictionary's keys, and displaying the tooltip from corresponding value pair.
Though, I am able to generate these labels as said above, I am not sure, how should I be displaying them in a way such Label1 Content ; Label2 Content ; Label3 Content
Is this the correct approach of doing this thing? And how should the labels be displayed according to the above layout (in a stack panel or something? how would these be separated by ; if I have the labels set as the children of stack panel). I am not quite sure.

You will want to use an ItemsControl, with its ItemsPanel set to a StackPanel with Orientation of Horizontal.
Have a look here - Use different template for last item in a WPF itemscontrol.
This shows a solution in XAML that will allow you to omit a semi-colon after the final item in the ItemsControl.
The other thing you may wish to do is convert your dictionary into an ObservableCollection of an 'Item' type or something similar, which means you can use property names in your ItemControl template bindings, which is neater. Something like:
var items = dictionary.Select(kv => new Item { Name = kv.Key, Value = kv.Value });
this.Items = new ObservableCollection<Item>(items);
Where Items is an ObservableCollection<Item> type, and Item is defined as:
public class Item
{
public string Name { get; set; }
public int Value { get; set; }
}
Note that if you instantiate the ObservableCollection in anything other than the constructor, then your Items property setter should invoke the PropertyChanged event, so that the UI is notified of the change.
I don't know how many items you are rendering, but you could also use a WrapPanel, which would allow the Labels to wrap after they reach a certain width. Note that the WrapPanel implemented in WPF doesn't virtualise though, so it will not perform as well for a large number of items.

Related

WinForms ListView - get source object from selected string item

I am building a simple language learning helper application in WinForms. One of its modules is a dictionary. It consists of "Sets" and words are stored in a Set. User can create a new Set of words and store some of them in it.
I'm printing all the words from selected one or several Sets in a ListView with columns. Upon checking a Set or Sets in CheckedListBox the List clears and prints words (as string variables).
Trouble comes when there are few Sets checked with their words listed, and user wants to edit one of the listed words. I cannot use indexes (such as index of List item equals to word item in a Set), as those List string items are from different Sets.
Is there any way get a source object from a ListView item? I have not added objects to the list but only some of their variables, but are they somehow connected?
Thank you for your help.
Cheers!
EDIT: Explaining why to me setting a Tag is not a solution:
All the Sets are stored in a List<DictionarySet> dictionarySets. Every Set containts a List<Word> words where words are stored.
How do I fill the ListView:
private void UpdateList()
{
wordsListView.Items.Clear();
List<Word> currentSetWordList;
foreach (DictionarySet ds in setCheckedListBox.CheckedItems) //List<DictionarySets> dictionarySets inserted, DisplayMember set to its Name property
{
currentSetWordList = ds.words;
foreach (Word w in currentSetWordList)
{
ListViewItem newItem = new ListViewItem("--"); //for now a string, later an enum
newItem.Tag = ds;
newItem.SubItems.Add(w.GermanTranslation); //string property
newItem.SubItems.Add(w.PolishTranslation); //string property
wordsListView.Items.Add(newItem);
}
}
}
In this case the program loops through each Set and its word list and prints the words. Their tag is DictionarySet everywhere.
You could use the Tag property on the ListViewItem. The Tag property:
Gets or sets an object that contains data to associate with the item.
In short, when you create each item of the list view, you could add your object as the tag. This way you'll be able to get it back when the user selects an item.
See MSDN for details.

Can WPF Data Bind Without Going Through a Windows Control?

This will take some explaining.
I'm writing a tool in WPF / C# to dynamically generate, in the view, a visual graph of the data in the view-model. The top-most parent is a grid, and each horizontal row of data is a canvas (inside a border). The canvas holds all the other UI elements (like TextBlocks).
I have a class to hold each row of UI elements in the view.xaml.cs, defined like this:
class ReportRow
{
public Border Divider;
public Canvas Row;
public TextBlock Title;
public List<TextBlock> Phases = new List<TextBlock>();
}
Then I define the entire graph as a List of these Rows:
List<ReportRow> reportRows = new List<ReportRow>();
In the viewmodel.cs, I have the data listed as an ObservableCollection so I can data bind to it and access the data from the view:
public ObservableCollection<SDDeliverable> Deliverables
{
get
{
return this.deliverables;
}
private set
{
this.deliverables = value;
this.RaisePropertyChanged(() => this.Deliverables);
}
}
Back in the view, I loop through the ObservableCollection, creating the rows and assigning the data to the elements (shown without all the styling and positioning, for brevity):
reportRows.Add(new ReportRow());
reportRows[i].Divider = new Border();
ProjectDisplay.Children.Add(reportRows[i].Divider);
reportRows[i].Row = new Canvas();
reportRows[i].Divider.Child = reportRows[i].Row;
reportRows[i].Title = new TextBlock();
reportRows[i].Title.SetBinding(TextBlock.TextProperty, new Binding(string.Format("Deliverables[{0}].DeliverableTitle", i)));
reportRows[i].Row.Children.Add(reportRows[i].Title);
Now, my original problem was that, because I'm binding each individual member of the collection (rather than binding the whole collection to one UI element, like a ListView), the view has no idea how long the collection is, which means I can't use a "foreach" or a loop counter. It worked fine with an arbitrary number of rows, but I didn't want to have to guess.
What I did was add a new label to the UI, bound to the length of the collection, and disguised it as a bit of helpful info:
<Label x:Name="DeliverableCountLabel" Content="{Binding Path=DeliverableCount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
this.DeliverableCount = Deliverables.Count;
Once I got that number into a view control, I was able to use it as my loop counter:
int totalItems;
if (int.TryParse(DeliverableCountLabel.Content.ToString(), out totalItems))
{
for (int i = 0; i < totalItems; i += 1)
{
reportRows.Add(new ReportRow());
This is a hacky fix, but I was alright with using it once.
The new problem is that once wasn't enough. As I add more data to the model, I'm hitting the same problem. Each Deliverable has a list of Phases and each Phase has a list of Tasks. I don't want to clutter up the UI with number labels all over the place.
I feel like there should be a way to use a data binding without having it go through the xaml or a visual control element. I just want to bind a variable in the view to a variable in the view-model so I can look at certain bits of info that I don't necessarily want to show the user.
I started messing with doing it this way:
Binding testBinding = new Binding("DeliverableCount");
However, it's the next step that's confounding me. Everything I've tried past that point has been incorrect somehow.
// returns the binding object itself, not the bound value
testBinding.ToString();
// error (not a real thing you can do, apparently)
string testString;
testString.SetBinding (testBinding);
How do I send a value from view-model to view without having to display it on-screen somewhere? Am I going about this the wrong way? Is this even possible?
A last-ditch idea I have is to create one dummy label and either make it invisible somehow or hide it behind another element. Then I could write a function to update the data binding on this one specific label any time I needed to access something in the view-model that's not shown on-screen. However, this really feels like a hack of a hack and I'd rather not go down that road unless it's really the best (or only) option.
This is how I ended up solving this. It's hacky, but it works.
I created a label in the xaml and set it's visibility to hidden. Then I just call one of these functions:
public string TempStringBind(string bind)
{
DummyLabel.SetBinding(Label.ContentProperty, new Binding(bind));
return DummyLabel.Content.ToString();
}
public int TempIntBind(string bind)
{
DummyLabel.SetBinding(Label.ContentProperty, new Binding(bind));
int newInt;
if (DummyLabel.Content != null && int.TryParse(DummyLabel.Content.ToString(), out newInt))
{
return newInt;
}
else
{
return -1;
}
}
This will take any variable from the view-model that can be bound to, bind it to an invisible label, grab that value from the label, and return it to me in the view in a useable form. While it's still going through something in the view xaml, the benefit is that the user doesn't have to see a bunch of extra controls or data they don't care about just to find out how many rows or columns I need to make for lists.
The ItemsSource method would be a lot cleaner, but that only works if I'm sticking the data in an existing control, like a ListView or a ComboBox, which aren't good for making visual charts and graphs with exact positioning.
I'm not sure what you guys meant by it not being MVVM. I've got the M, the V, and the VM all in there. :P

C# Combobox Displaying Blank Items

I'm coding a combobox in C# and for some reason the items in the drop down don't have text. When I have selected an item, it is shown in the combo box text field (the drop down list is always blank whenever I click the drop down button). The datasource seems bound properly because the proper values are being returned when I select items, and the size of the drop down list will change depending on how many items the datasource has. Everything looks fine except for the fact that it seems like my drop down is populated with a bunch of empty strings, which it clearly isn't since as soon as an item is selected the proper text will display.
This is the relevant code:
if (list.Count > 0)
{
cboCustomers.DisplayMember = "Name";
cboCustomers.DataSource = list;
cboCustomers.ValueMember = "ID";
cboCustomers.SelectedIndex = 0;
}
I have looked for an answer to this but can't find it anywhere...I'm sure it's something really simple, but I can't figure it out. The closest problem I found had an answer suggested to set the display member before the data source, which clearly didn't work.
The list is populated from a database query. This will run on keyUp, the idea is that the list is populated as the person is typing based on the info given. So if I wrote 'S' I'd get a combobox with a dropdown that had all the clients starting with 'S'.
Given you don't have any anomalies in your binding, you are probably being affected by DrawMode property of your ComboBox, which may be set to OwnerDrawFixed or OwnerDrawVariable. Set it to Normal and things should get better.
as soon as an item is selected the proper text will display.
A foreground color the same as the background color will produce the same results you are seeing.

WP7: Styling ListBox items

I'm new to Windows Phone 7 development. I need to create a page very similar to the settings app page. Something like this (but without the menu at the top and the subtext for each item):
(source: dotnetapp.com)
So far I've got a listbox with items, but clicking on one of the items, the item color changes and it doesn't have the "pushed button" effect like the settings application has.
First question is how do I create this beautiful pushed button effect (notice that the button tilts when pressed depending on the position of the click).
My second question is about styling items differently. The ItemsSource of the listBox is defined like this:
List<string> firstList;
List<string> secondList;
public MainPage()
{
...
List<string> lst = new List<string>();
lst.AddRange(firstList);
lst.AddRange(secondList);
listBox1.ItemsSource = lst;
...
I need to style the items differently whether they come from firstList or secondList, for example if the item is from firstList its color should be blue.
I think it should be done using StaticResource, but i'm not sure. Maybe I'll need to somehow wrap the string so that it will have a getter for defining from which list it comes from.
Thanks.
Question 1 is answered (see William Mekanis comment)
For question 2 you have one big problem... you are binding a list of strings... no change to see which item is coming from which list.
I would create something like a view model for my DataSource list.
Something like (NotifyPropertyChanged is ignored here, implement it if needed and use an ObservableCollection also ;) ):
public class ListDataSourceViewModel
{
public string Text {get; set;}
public bool IsFromFirstList {get; set;}
}
In case you have more lists you could also use an enum or whatever as list identifier...
That you create a new list for the DataSource like:
lst.AddRange(firstList.Select(item => new ListDataSourceViewModel
{
Text = item, IsFromFirstList = true
}
).ToArray());
lst.AddRange(secondList.Select(item => new ListDataSourceViewModel
{
Text = item, IsFromFirstList = false
}
).ToArray());
Afterwards create a datatemplate for your listitem binding the text to a textblock and the font color for your textblock to the IsFromFirstList property using a converter.
This code is written from mind, without VS... not shure if u can copy paste without problem but it should give you the idea ;)
If you need help with creating the datatemplate and the converter just tell me!
Edit:
I rethought my suggestion... using converters, specialy in (potential) large lists, is not a good idea (for performance point of view). In your case it is anyway not a problem to use the needed color directly in the viewmodel.
I would change
public bool IsFromFirstList {get; set;}
to
public Color WhatEverColor {get; set;}
set it as needed when the VMs are created and bind it to wherever you need it.
Hope it helps!

How to lazy-evaluate a wpf:DataGrid, retrieving data only as needed

I have a WPF datagrid, bound to a list populated by linq-to-sql from a database. The binding is two-Way, allowing the user to change the values in each row
<wpf:DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding MyList}"
SelectedItem="{Binding SelectedItem}" >
When displaying about 20000 rows, the program crashes with an out-of-memory exception during initialisation of the list. Performance becomes unbearably slow even with less rows.
I know that on initialization the datagrid iterates through each row to measure the maximum column width and other properties. It will apparently do so for all rows regardeless of whether they are on screen.
I tried either binding the datagrid to myQuery.ToList() (to allow sorting the datagrid by clicking on the columns) or binding directly to the IQueryable. (Sorting does not work with that)
Both produce the same result. The ToList() with 20000 items alone does not cause the massive memory consumption, this happens only when it is bound to the datagrid .
Ignoring the issue of how useful 20000 rows in a datagrid are (those are the current requirements; to change those a working example would be helpful).
What is the most simple way to lazily load only the data currently shown on the screen, and ignore everything else until it is scrolled into view?
Can this be done without third party libraries and major code changes?
If not, what would be the recommended workaround?
It turns out that the problem was entirely user error on my part:
WPF Datagrid can do UI virtualisation just fine: The memory consuming row objects are only drawn when required; if a row is outside the visible bounds of the datagrid, it will not be instantiated.
This will however not work if the datagrid is contained inside a ScrollViewer.
Inside a scrollviewer every part of the datagrid is virtually visible, so the entire datagrid will be rendered. The scrollviewer then shows only the part of this rendered datagrid that fits inside the UI window.
Since a datagrid inside a scrollviewer looks just like a datagrid that manages its own scrollbars, I did not notice the scrollviewer.
With the scrollviewer removed, large amounts of rows cause no problem at all, even with variable height and width. The datagrid simply fills the available space, and only instantiates new rows as required.
So in short, the solution for my problem was: Do not put a datagrid inside a scrollviewer
The property that you are binding in this his case let's say that your MyList is made up of MyFile objects (List<MyFile>) then you have to create your MyFile class as:
class MyFile
{
public string FullPath { get; set; }
public string Extension
{
get
{
return Path.GetExtension(FullPath);
}
}
public string PathRoot
{
get
{
return Path.GetPathRoot(FullPath);
}
}
public DateTime CreationTime
{
get
{
return File.GetCreationTime(FullPath);
}
}
}
That way you will store less info in each object and jut call the get method on the few items that are displayed in the grid. Versus having the actual values stored in the class. Hope this helps

Categories