How to make VariableSized GridView Item? - c#

I would like to do a VariableSizedWrapGrid for the item within the wrapgrid.
Something like this.
The group title above store all the child item show on photo.
After scroll to right hand, another group title with the child shown as below.
Anyone have any idea how to do so?
I was able to display group title with child item as below. The only things i unable to achieve is the variable size of the child item.

One way you can go is to use the GridView's built-in selector properties.
See my blog entry.
In a nutshell, you can create a custom StyleSelector. All you have to do is override thee StyleSelectorCore() method and put in your logic to choose a style that defines the column or row spans.
You'll need to get the default GridViewItem style template through Blend or an online resource and create a default explicit style. Then create new styles BasedOn the explicit one like so:
<Style x:Key="DoubleHeightGridViewItemStyle"
BasedOn="{StaticResource DefaultGridViewItemStyle}"
TargetType="GridViewItem">
<Setter Property="VariableSizedWrapGrid.RowSpan"
Value="2" />
</Style>
For this to work, you'll also need to change the GridView's ItemsPanel template to use a VariableSizedWrapGrid.
Finally, by creating a custom DataTemplateSelector, you'll be able to change the DataTemlates of your bound items. You'll need to do this unless your over-sized items can use the same DataTemplate as the default sized one.

Updating this because some other helpful things have emerged since the question was asked. My colleague Jerry Nixon has a nice post describing how to create variable-sized items in a GridView:
Windows 8 Beauty Tip: Using a VariableSizedWrapGrid in a GridView makes Grids Prettier
Short version, you can make a custom GridView that implements PrepareContainerForItemOverride.
There's also more details in an earlier example (from May) that Mark Rideout posted:
How To: Create a Variable Sized Grouped GridView (like the store)

Here is a solution for doing it in C#: http://leeontech.wordpress.com/2012/03/01/customizing-gridview-items-in-metro-app-4/

I am not sure about C# and XMAL code of doing this. But in Javascript, instead of creating the template in HTML, you can create the item template in javascript by doing something like this
function MyItemTemplate(itemPromise) {
return itemPromise.then(function (currentItem) {
var result = document.createElement("div");
//use source data to decide what size to make the
//ListView item and apply different css class to it
result.className = currentItem.data.type;
result.style.overflow = "hidden";
// Display image
var image = document.createElement("img");
image.className = "regularListIconTextItem-Image";
image.src = currentItem.data.picture;
result.appendChild(image);
var body = document.createElement("div");
body.className = "regularListIconTextItem-Detail";
body.style.overflow = "hidden";
// Display title
var title = document.createElement("h4");
title.innerText = currentItem.data.title;
body.appendChild(title);
// Display text
var fulltext = document.createElement("h6");
fulltext.innerText = currentItem.data.text;
body.appendChild(fulltext);
result.appendChild(body);
return result;
});
}
Source of this code is in the ListView Item Templates sample of the consumer preview sample package. Unfortunately, I was not able to find the C# version of it.
Hope this helps to some degree.

Related

How can I create a DataGrid with dynamic columns and cell templates?

I liked this approach to the solution, except that it will not work with virtualization.
Here's the relevant code from that post, abbreviated:
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
DataRowView dataRow = (dataItem as DataRowView);
object cellData = dataRow[cell.Column.DisplayIndex];
var contentHost = new ContentControl() { Content = cellData };
contentHost.ContentTemplate = (DataTemplate)SomeResourceDictionary["SomeResourceKey"];
return contentHost;
}
This method is called by the DataGrid for every cell created/rendered. If virtualization is enabled, then the FrameworkElement created here may be recycled, i.e. used again.
The problem: using the information stored in cell and dataItem the content is determined and a direct reference is passed to the created ContentControl. If this FrameworkElement is reused, it will reference the same content - regardlesss of which row it is being rendered for.
Result: during (quick) scrolling the content displayed in the grid rows will randomly be mixed up.
My question: using cell.Column.DisplayIndex can the ContentControl.Content binding be set without including a direct reference to the desired content? Maybe, as if I defined it in Xaml something like this: "{Binding Path=dataRow[0]}" - but that also doesn't seem quite right.
Any suggestions greatly appreciated!
#BionicCode:
There are two challenges addressed here:
Binding a DataGrid to a source with dynamic columns.
Allow a DataTemplate to be defined for each dynamically generated column.
The first issue is covered quite well in the post linked above: create a DataTable from the original source and bind the DataGrid to that. The difficult part of the second issue - and why DataGridTemplateColumns cannot simply be used - is that DataGridTemplateColumns receive the entire row item as data context, i.e. to access the correct data, the template would have to know which column it is being used in.
This is where the code solution shown above comes in, by manually generating the FrameworkElement and "statically" assigning the desired content.
Luckily, the fix is rather simple, see my answer below.
I knew the solution had to be close and, luckily, it was. In the code above replace the following line...
var contentHost = new ContentControl() { Content = cellData };
...with these:
var contentHost = new ContentControl();
BindingOperations.SetBinding(contentHost, ContentControl.ContentProperty, new Binding("[" + cell.Column.DisplayIndex + "]"));
Now the ContentControl can dynamically bind to the correct column and virtualization works. At least EnableRowVirtualization, I haven't tested EnableColumnVirtualization, yet.

how to identify the first, second and third element when the object names are the same in CodedUI tests

I am writing CodedUI tests for a web application. There are three text boxes with the same name and I would like to know how do we call these text boxes ? Please advise ?
Use this:
var control = new HtmlControl(parent)
control.SearchProperties.Add([Control Type], [Control Name]);
var specificControl = control.FindMatchingControls()[index]
In the above code, what it does is find the three controls you mentioned with the same name, and then index them in a collection. By taking a piece of that collection with "[index]", you can isolate a single control.
This is what it looks like in practice in a WPF app:
//Identify the cell and minimize button 2017
WpfCell currentyearCell = new WpfCell(workWindow);
currentyearCell.SearchProperties.Add(WpfCell.PropertyNames.Value, DateTime.Now.AddYears(0).ToString("yyyy"));
currentyearCell = currentyearCell.FindMatchingControls()[0] as WpfCell;
If 3 elements has same property and if you will provide a search properties
Control.searchproperties.add("","")
And you intend to select 2 element.Than by this approach it will automatically identify the first element.
Just go for next sibling search configuration.
So it will go the next element or we can use children element[pass the index]

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# generics. Trying to set columns generically for a grid control

I need some help on how to set the list of columns for a a grid control generically. I have a 3rd party library with the following method signature I want to use:
public GridControl SetColumns<T>(Action<GridColumnModelList<T>> initCols)
In my Controller on the action method I get a generic model the contains the list of columns I want displaying:
var gridprofile = new GridProfile<SiteVisitSearchGridViewModel>(gridProfileid);
I now want to create the grid control and apply my columns to it but dont know how to do this:
GridControl gc = new GridControl();
gc.SetColumns<SiteVisitSearchGridViewModel>(gridprofile.Columns);
help please
thanks
Andy
you could try something(it may not compile - I don't have that library installed) like this:
gc.SetColumns<SiteVisitSearchGridViewModel>(columns =>
{
foreach(col in gridprofile.Columns)
columns.Add(x => new GridColumnModel(col.Name));
});

Multiple columns in List box control

How can I display two columns in a list box?
A list box wasn't designed to display multi-column data. Even the Windows Forms version doesn't directly support that kind of data display.
Your requirements aren't clear, but the simplest way to go would be to use a GridView control. It gives you a lot of functionality out of the box, and you can expand it to more columns very easily. If you need more control over the look or functionality, you can use a DataList instead.
To get the scrolling ability, you can either use a scrolling <div> or simply use the pagination mechanism of the GridView if that's appropriate.
You could line it up as if the data was in 2 columns
new ListItem("blah1".PadRight(10, ' ') + "blah2");
as shown here: http://articles.dotheweb.net/post/Formatting-columns-in-a-ListBox-of-ComboBox.aspx
Also, you could roll your own with a DataList.
If you want to use columns in a ListBox, you have to do it based on alignment.
For example:
String columns = "{0, -55}{1, -35}{2, -35}";
ListBox1.Items.Add(String.Format(columns, "Filename", "Selected DateModified", "Vault DateModified"));
ListBox1.Items.Add(String.Format(columns, fileName, datetime1, datetime2));
Output of my own implementation of this code below:
Keep in mind the font you use has to be a monospaced font, otherwise the alignment will mess up due to variable spacing between characters (and this exaggerates the longer the string is).
Looks like you should write your own control, or you can use the listview control.
As Nick Craver has already commented, the ListView probably isn't the right control for multi-column information.
Instead of hacking your list to appear as if it has two columns, it might be a better idea to use a DataGridView. It'll be easier to setup, format, and your data will be held in a much more flexible way.
DataGridViews also support assigning Lists of objects as datasources, if that makes things easier.
use list view it is perfect alternative for multi column list box
If I understood correctly, you want a data column to display horizontal. This can be acheived by using a DataList and have RepeatDirection set to "Hozizontal" with the specified repeat columns.
Eg :
<asp:DataList ID="DataList1" runat="server" RepeatDirection="Horizontal" RepeatColumns="5" CellSpacing="10" >
Multiple items side by side are possible if you reference the toolkit and add the wrapPanelOrientation ;)
it will list look like
1stItem 2ndItem
3rdItem 4thItem .. ect..
ListBox.ItemsPanel>
ItemsPanelTemplate>
toolkit:WrapPanelOrientation="Horizontal"FlowDirection="LeftToRight"ItemWidth="220"ItemHeight="60"/>
/ItemsPanelTemplate>
/ListBox.ItemsPanel>
/ListBox>

Categories