In WPF, accessing containers within ListBox - c#

I'm creating a DerivedListBox : ListBox and a DerivedHeaderedContentControl : HeaderedContentControl, which will serve as a container for each item in the ListBox.
In order to calculate the size available for the expanded content of the DerivedHeaderedContentControls, I am storing each container object in a list within the DerivedListBox. This way I can calculate the height of the headers of each DerivedHeaderedContentControl and subtract that from the total size available to the DerivedListBox. This would be the size available for the expanded content of a DerivedHeaderedContentControl.
public class DerivedHeaderedContentControl : HeaderedContentControl
{
// Do some binding to DerivedListBox to calculate height.
}
public class DerivedListBox : ListBox
{
private List<DerivedHeaderedContentControl> containers;
protected override DependencyObject GetContainerForItemOverride()
{
DerivedHeaderedContentControl val = new DerivedHeaderedContentControl();
this.containers.Add(val);
return val;
}
// Do some binding to calculate height available for an expanded
// container by iterating over containers.
}
The problem comes in when the DerivedListBox's ItemsSource is cleared (or an item in the items source is removed). How can I determine when the ItemsSource is cleared so that I can clear the containers list?

For this particular scenario, you should probably use ItemsContainerGenerator.ItemsChanged event.

Related

How to get UIElement out of templated data in ListView?

Okay, i feel slightly dumb for askin this but, I have a listview with a templated class MyClass or whatever, whenever i "myListView.Add(new MyClass())" the winrt platform adds a new UIElement there and binds the proper properties into their proper uielements properly, now, I want to be able to iterate through these logical items (myListView.Items or myListView.SelectedItems) and get their corresponding UIElement for animation, is that possible?
like for example
class PhoneBookEntry {
public String Name { get;set }
public String Phone { get;set }
public PhoneBookEntry(String name, String phone) {
Name = name; Phone = phone;
}
};
myListView.Add(new PhoneBookEntry("Schwarzeneger", "123412341234");
myListView.Add(new PhoneBookEntry("Stallone", "432143214321");
myListView.Add(new PhoneBookEntry("Statham", "567856785678");
myListView.Add(new PhoneBookEntry("Norris", "666666666666");
And in XAML (just an example so i can explain what I mean)
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Phone}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
So, my point and objective here is to
foreach(PhoneBookEntry pbe in myListView.Items) // or SelectedItems
{
UIElement el; // How can I get the UIElement associated to this PhoneBookEntry pbe?
if(el.Projection == null)
el.Projection = new PlaneProjection;
PlaneProjection pp = el.Projection as PlaneProjection;
// Animation code goes here.
if(myListView.SelectedItems.Contains(pbe)
//something for selected
else
//something for not selected
}
I just need a way to get an UIElement which is being used to represent this logical data class PhoneBookEntry in the templated listview.
Also, this necessity comes with a very big problem I'm having where, selected items doesn't differ visually on Windows Phone -_- any ideas?
You can also use the ListView.ContainerFromItem or ListView.ContainerFromIndex methods which will return the container UI element for a given item in the list view (of course, only if the container is generated)
Ok I may look like a fool answering my own question but i've figured a way out.
First things first: ListViews only create UIElements for determinate items in the list (the ones cached and the ones being shown). So if you do add 2000 items to myListView.Items, the effective ammount of UIElements representing these items will be 56 or close number.
Because, the ItemListView simulates the UIElements even if they're not there, just to give size and position to the scrollbar (hence why scrolling down on very large lists cause some lag, WinRT is unloading UIElements and loading new ones)
From that, I figured out I could simply iterate through the current list of loaded UIElements through
// For each of the cached elements
foreach(LIstViewItem lvi in myListView.ItemsPanelRoot.Children)
{
// Inside here I can get the base object used to fill the data template using:
PhoneBookEntry pbe = lvi.Content as PhoneBookEntry;
if(pbe.Name == "Norris")
BeAfraid();
// Or check if this ListViewItem is or not selected:
bool isLviSelected = lvi.IsSelected;
// Or, like I wanted to, get an UIElement to animate projection
UIElement el = lvi as UIElement;
if(el.Projection == null)
el.Projection = new PlaneProjection();
PlaneProjection pp = el.Projection as PlaneProjection;
// Now I can use pp to rotate, move and whatever with this UIElement.
}
So, this is it. Right beneath my nose...

Check if an element is added on a Grid

I have a UserControl contained in a class When some conditions are met I initialize the UserControl and I add it in a Grid, setting row and column property depending on the position of the class in a list. When I change that position, I need to update the position of the element on the Grid, but since not every class has its UserControl initialised and added, I need to check when I order the elements in the Grid.
To check if the UserControl is initialised I check if it is null
classElement.userControl == null
How do I check if the UserControl has been added to the Grid?
Perhaps the easiest way to achieve your requirements would be for you to simply iterate through the children of the Grid using the Children property, checking each one in turn. You could do something like this:
<Grid Name="YourGrid">
...
</Grid>
...
foreach (UIElement uiElement in YourGrid.Children)
{
if (uiElement.GetType() == typeof(UserControl))
{
if (uiElement != null)
{
// Do something with your control here
}
}
}
UPDATE >>>
I don't understand your comment... you said I need a way to know if the usercontrol is in the grid or not... that is exactly what I have provided you with. If you are adding the UserControl to the Grid, then you must have a reference to the Grid. If you have a reference to the Grid, then you can iterate the controls that have been added to that Grid just as I have shown you. If you are adding the UserControl from the UserControl code behind, then you can do this:
foreach (UIElement uiElement in YourGrid.Children)
{
if (uiElement.GetType() == typeof(UserControl))
{
if (uiElement != null)
{
if (uiElement == this)
{
// this UserControl is in the Grid
}
}
}
}
If this does not solve you problem, then please take the time to provide a decent description of your problem that would enable me to suggest a fix for it. From what I understand presently, this is your fix.

Different-sized Tiles in Windows App

I know that I can use the ListView and GridView to create "Tiles"/Items whatever size I want, but how can I create different-sized Tiles for use within my app? This will need to work with a ListView or GridView.
I have tried so many things but I just have absolutely no idea how to do this. Any help will be much appreciated.
In case I haven't described what I am trying to achieve properly, here is a pic:
A simple way is to create a new class inheriting from GridView and override the PrepareContainerForItemOverride method. In which you can set the Column Span and RowSpan to Child item based on the model data. Consider your model class contains the Spanning information.
public class VariableGrid : GridView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
ITileItem tile = item as ITileItem;
if (tile != null)
{
GridViewItem griditem = element as GridViewItem;
if (griditem != null)
{
VariableSizedWrapGrid.SetColumnSpan(griditem, tile.ColumnSpan);
VariableSizedWrapGrid.SetRowSpan(griditem, tile.RowSpan);
}
}
base.PrepareContainerForItemOverride(element, item);
}
}
More information : http://wpfplayground.blogspot.in/2013/03/different-sized-tile-items-in-winrt.html
You need to set ItemsPanel/ItemsPanelTemplate of your list to VariableSizedWrapGrid and set Grid.RowSpan/ColumnSpan of your list items to the values you want. I believe you can do that in ItemContainerStyle of the list control, which is best extracted by right-clicking the control in VS XAML design view or in Blend and selecting "Edit Additional Templates"/"Edit Generated Item Container".

WPF Listbox Virtualization creates DisconnectedItems

I'm attempting to create a Graph control using a WPF ListBox. I created my own Canvas which derives from a VirtualizingPanel and I handle the realization and virtualization of items myself.
The listbox' item panel is then set to be my custom virtualized canvas.
The problem I am encountering occurs in the following scenario:
ListBox Item A is created first.
ListBox Item B is created to the right of Item A on the canvas.
ListBox Item A is virtualized first (by panning it out of view).
ListBox Item B is virtualized second (again by panning it out of view).
Bring ListBox Item A and B in view (i.e: realize them)
Using Snoop, I detect that the ListBox has now 3 items, one of them being a "DisconnectedItem" located directly underneath ListBox Item B.
What causes the creation of this "DisconnectedItem" ? If I were to virtualize B first, followed by A, this item would not be created. My theory is that virtualizing items that precedes other items in a ListBox causes children to be disconnected.
The problem is even more apparent using a graph with hundreds of nodes, as I end up with hundreds of disconnected items as I pan around.
Here is a portion of the code for the canvas:
/// <summary>
/// Arranges and virtualizes child element positionned explicitly.
/// </summary>
public class VirtualizingCanvas : VirtualizingPanel
{
(...)
protected override Size MeasureOverride(Size constraint)
{
ItemsControl itemsOwner = ItemsControl.GetItemsOwner(this);
// For some reason you have to "touch" the children collection in
// order for the ItemContainerGenerator to initialize properly.
var necessaryChidrenTouch = Children;
IItemContainerGenerator generator = ItemContainerGenerator;
IDisposable generationAction = null;
int index = 0;
Rect visibilityRect = new Rect(
-HorizontalOffset / ZoomFactor,
-VerticalOffset / ZoomFactor,
ActualWidth / ZoomFactor,
ActualHeight / ZoomFactor);
// Loop thru the list of items and generate their container
// if they are included in the current visible view.
foreach (object item in itemsOwner.Items)
{
var virtualizedItem = item as IVirtualizingCanvasItem;
if (virtualizedItem == null ||
visibilityRect.IntersectsWith(GetBounds(virtualizedItem)))
{
if (generationAction == null)
{
GeneratorPosition startPosition =
generator.GeneratorPositionFromIndex(index);
generationAction = generator.StartAt(startPosition,
GeneratorDirection.Forward, true);
}
GenerateItem(index);
}
else
{
GeneratorPosition itemPosition =
generator.GeneratorPositionFromIndex(index);
if (itemPosition.Index != -1 && itemPosition.Offset == 0)
{
RemoveInternalChildRange(index, 1);
generator.Remove(itemPosition, 1);
}
// The generator needs to be "reseted" when we skip some items
// in the sequence...
if (generationAction != null)
{
generationAction.Dispose();
generationAction = null;
}
}
++index;
}
if (generationAction != null)
{
generationAction.Dispose();
}
return default(Size);
}
(...)
private void GenerateItem(int index)
{
bool newlyRealized;
var element =
ItemContainerGenerator.GenerateNext(out newlyRealized) as UIElement;
if (newlyRealized)
{
if (index >= InternalChildren.Count)
{
AddInternalChild(element);
}
else
{
InsertInternalChild(index, element);
}
ItemContainerGenerator.PrepareItemContainer(element);
element.RenderTransform = _scaleTransform;
}
element.Measure(new Size(double.PositiveInfinity,
double.PositiveInfinity));
}
I'm 6 years late, but the problem is still not fixed in WPF. Here is the solution (workaround).
Make a self-binding to the DataContext, eg.:
<Image DataContext="{Binding}" />
This worked for me, even for a very complex xaml.
It's used whenever a container is removed from the visual tree, either because the corresponding item was deleted, or the collection was refreshed, or the container was scrolled off the screen and re-virtualized.
This is a known bug in WPF 4
See this link for known bug, it also has a workaround you may be able to apply.
"You can make your solution a little more robust by saving a reference
to the sentinel object {DisconnectedItem} the first time you see it,
then comparing against the saved value after that.
We should have made a public way to test for {DisconnectedItem}, but
it slipped through the cracks. We'll fix that in a future release, but
for now you can count on the fact that there's a unique
{DisconnectedItem} object."

How to update a custom dependency property when the datasource list changes

We have a user control with a custom dependency property (DP). The DP is bound to an ObservableCollection.
When a new item is added to the collection programatically, the databinding does not update the target DP. Why? We think it's because, unfortunately, in our case the target is not a ListBox or ListView, but a Canvas. The DP, when changed or initialized, is supposed to draw a Shape (!) onto the Canvas, and the shape's position and size is bound to the collection item's two properties: WIDTH, LEFT.
Ideally we don't want to clear the Canvas and redraw all items just becasue one has been added (or deleted). But how?
So:
How can the custom DP take care of drawing the shape for the new collection item? What callback do we need, at what point in time does this have to happen, and what specific MetaDataOptions might there?
Also, are there any good resources out there concerning all these dependency property options. They are quite confusing. MSDN does not really help with what we're trying to do.
Thanks!
EDIT:
The ObservableCollection is like so:
public class Projects : ObservableCollection<Project>
{
//no ommitted code. this class really IS empty!
}
The DP is like so:
public class MyUserControl : UserContorl
{
public static readonly DependencyProperty... etc. typeof(Projects)
private static void OnProjectsChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyUserControl u = d as MyUserControl;
CpProjectCollection projects = e.NewValue as CpProjectCollection;
u.Refresh(projects);
}
private void Refresh(CpProjectCollection projects)
{
foreach (CpProject p in projects)
{
//...add each project to the Canvas
ProjectContorl pc = new ProjectControl();
pc.Project = project;
Binding b = new Binding("StartTime");
b.Converter = new TimeSpanConverter();
b.Source = pc.Project;
b.Mode = BindingMode.TwoWay;
c.SetBinding(Canvas.LeftProperty, b);
//do the same for project duration
}
}
}
If you bind to ObservableCollection, you get the change notification if the collection is replaced with another collection, not when the collection's content is changed. So, you'll need to subscribe to CollectionChanged event in your code-behind.
If you subscribe to CollectionChanged, you can see which are the new/deleted items in your ObservableCollection. You can add a new shape for each new item and remove old shapes for deleted items.

Categories