WP7: Styling ListBox items - c#

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!

Related

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

Windows Forms - Data Binding to a DataSource properties's property

I've got a data binding source, and it has an associated property. When this source property is text/integer based etc, everything works fine. I can bind that to a text box or label okay.
Some of my properties are lists. This is also fine, I can create a new listBindingSource, point it's datasource at the origional binding source, and the datamember as the list and point my, say, listview at that. I set the DisplayMember to the property of the item in the list and it works fine.
However, if the property is neither list, or text/int, but just a single object... I'm stuck. There's no way of telling it I want to bind the properties's property to the object. There's no 'DisplayMember' to help.
An example:
I have an object MeteredSpace. It has the properties
public string Name {get;set;}
public List<MeteredSpace> ChildMeteredSpaces {get;set;}
public MeteredSpace ParentMeteredSpace {get;set;}
Then I define the following met
//underlying datasource
this.meteredSpaceBindingSource.DataSource = typeof(SEMS.LinqObjects.MeteredSpace);
//data source of children
this.childMeteredSpacesBindingSource.DataMember = "ChildMeteredSpaces";
this.childMeteredSpacesBindingSource.DataSource = this.meteredSpaceBindingSource;
Now for my controls:
//nice and easy, picking directly from the underlying data source
this.nameTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text",this.meteredSpaceBindingSource, "Name", true));
//our child metered space list box is pretty easy too:
this.meteredSpaceListBox.DataSource = this.childMeteredSpacesBindingSource;
this.meteredSpaceListBox.DisplayMember = "Name";
//then we come to our parent text box
this.nameTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text",this.meteredSpaceBindingSource, "ParentMeteredSpace", true));
I have no way of pointing it at ParentMeteredSpace's Name property like I do with the list box. I also can't create a nice new binding source for it.
Anyone know of a way around this? I had a look into overriding a control and adding my own DisplayMember, but I started to think there might be a better way of doing it.
Thanks
You should be able to reference through the parent property:
Binding b = new Binding("Text",
this.meteredSpaceBindingSource,
"ParentMeteredSpace.Name",
true);
this.nameTextBox.DataBindings.Add(b);

Should I use an ObservableCollection for Images

I am using a mvvm pattern for an application that sources its data from a sql ce database using the Entity Framework version 4. The WPF application only has one view (don't need anymore as the app is not that big). I am displaying a collection of properties from the database in a listbox, by creating an observablecollection in my viewmodel and binding this. This works exactly as expected. The issue is that I now have another listbox (in the same view) that needs to be populated with Images for each property. To be clear, each property has a bunch of images, but each image is only assigned to one property.
What would be the best way to display the images, I thought maybe creating another observablecollection for the images, but I am not sure how I would then ensure that only images for the appropriate property are shown. Or should I simply bind the listbox to the Images property of each property (house)?
Cheers
private void Load()
{
PropertyList = new ObservableCollection<Property>((from property in entities.Properties.Include("Images")
select property));
propertyView = CollectionViewSource.GetDefaultView(PropertyList);
if (propertyView != null)
propertyView.CurrentChanged += new System.EventHandler(propertyView_CurrentChanged);
RaisePropertyChanged("CurrentContact");
RaisePropertyChanged("SaleTitle");
RaisePropertyChanged("Address");
RaisePropertyChanged("AuctioneerName");
RaisePropertyChanged("AgentName");
RaisePropertyChanged("Price");
RaisePropertyChanged("NextBid");
RaisePropertyChanged("Status");
}
That sounds distinctly like a different responsibility (a master/details view). In the true spirit of MVVM I'd create a new View and a new ViewModel - perhaps:
PropertyImagesViewModel
- public Property Property { get; set; }
- public IList<Image> Images { get; set; }
- public int SelectedIndex { get; set; }
PropertyImagesView
Don't forgot to call RaisePropertyChanged() in each of the property setters
Also note that ObservableCollection does nothing if you aren't manipulating the contents one-at-a-time. If all do you is update the entire collection all-at-once, then it gives you no tangible benefit.
Another thing - if you need to notify that all your properties changed:
RaisePropertyChanged(null);
will do the trick.

Segmentation and displaying content from dictionary

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.

C# Update combobox bound to generic list

I have a combobox on my form that is bound to a generic list of string like this:
private List<string> mAllianceList = new List<string>();
private void FillAllianceList()
{
// Add alliance name to member alliance list
foreach (Village alliance in alliances)
{
mAllianceList.Add(alliance.AllianceName);
}
// Bind alliance combobox to alliance list
this.cboAlliances.DataSource = mAllianceList;
}
The user may then add or remove items in the combobox.
I have read elsewhere that by simply adding or removing the item in the generic list, the contents of the combobox should automatically be updated; same thing should occur if I use Sort() on it.
But for some reason, I cannot make this work. I can see the combobox's DataSource property is correctly updated as I add/remove/sort items, but the contents displayed in the combobox are not those in the DataSource property.
I am surely missing something or doing something wrong.
Thanks in advance!
EDIT:
The answer I chose solved the issue for adding and removing, but a BindingList object cannot be sorted, and this is necessary for me. I've found a solution where a custom class is built by inheriting BindingList and adding sorting capabilities, but I would like to know if there's an easier solution in my case.
Any suggestions on how to solve this easily?
The easiest way around this would be to simply use a BindingList like so:
private List<string> mAllianceList = new List<string>();
private BindingList<string> bindingList;
private void FillAllianceList()
{
// Add alliance name to member alliance list
foreach (Village alliance in alliances)
{
mAllianceList.Add(alliance.AllianceName);
}
bindingList = new BindingList<string>(mAllianceList);
// Bind alliance combobox to alliance list
this.cboAlliances.DataSource = bindingList;
}
Then, from here on out, just deal with the binding list to add and remove items from there. That will remove it both from the List and from the ComboBox.
EDIT: To answer your question regarding sorting, I guess the easiest (but possibly "hacky" way to do it would be something like this:
mAllianceList.Sort();
bindingList = new BindingList<string>(mAllianceList);
this.cboAlliances.DataSource = bindingList;
So basically, after you sort, you create a new binding list and reset the data source. Maybe there's a more elegant way to go about this, however this should work.

Categories