Given the feedback of my last question, I have decided to delete it and ask again with a more detailed description of what I'm trying to accomplish:
Old Question:
I've tried a lot of the event handlers for ItemControl only to find that everytime I tried to iterate the itemControl in it's event, it had not yet been bound by it's itemssource.
Is there a way to fire an event after the ItemControl's ItemsSource has been bound?
It has to be specific for ItemsControl not e.g. ListView.
What I wish to do, is iterate through an ItemsControl's items, that have been bound through itemssource. I wish to do this once and that has to be at some point after it's ItemsSource property has been bound through the ViewModel.
This could easily be done by using a ListView instead and using it's SelectionChanged event, but I wish to use an ItemsControl, as I am having problems using the VisualTreeHelper on a ListView.
A quick example of my code:
<ItemsControl x:Name="BoundToObservableCollection" ItemsSource="{Binding ElementName=CalendarDateItemName, Path=CalendarItemsShow}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="CatchThisControl"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#:
// I wish to execute this after the ItemsSoucr of my ItemsControl has been bound.
ItemsControl list = BoundToObservableCollection;
foreach (var item in list.Items)
{
var _Container = list.ItemContainerGenerator.ContainerFromItem(item);
var _Children = AllChildren(_Container);
}
public List<UIElement> AllChildren(DependencyObject parent)
{
var _List = new List<UIElement> { };
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var _Child = VisualTreeHelper.GetChild(parent, i);
if (_Child is UIElement)
_List.Add(_Child as UIElement);
_List.AddRange(AllChildren(_Child));
}
return _List;
}
In all of the events of ItemsControl, the itemssource is yet to be bound. How can I fire the AllChildren, after it's been bound?
Related
I am using the Treeview control of Windows.UI.Xaml.Controls with 'multiple' selection mode. Here I need to bind some property as SelectedItem in twoway binding to fetch the selected nodes into an enumerable collection.
But Windows.UI.Xaml.Controls.TreeView does not have selectedItem property or any selectionChanged event. How do I fetch the selected nodes?
<muxc:TreeView SelectionMode="Multiple" Width="300" x:Name="CategoriesTree"
ItemsSource="{x:Bind _vm.Categories, Mode=OneWay}">
<muxc:TreeView.ItemTemplate>
<DataTemplate x:DataType="local1:CategoriesInfo">
<TreeViewItem ItemsSource="{x:Bind SubCategories}" Content="{x:Bind Name}" />
</DataTemplate>
</muxc:TreeView.ItemTemplate>
</muxc:TreeView>
I tried to use TreeView of Microsoft.UI.Xaml.Controls in WinUi2 but it throws a runtime error
No such Interface supported
by Windows.Ui.Xaml. How do I do this?
As you can see in the documentation, there is no node selectionChanged event for now.
Even with TreeView.ItemInvoked Event
This event is not fired when the item's multiple-selection checkbox is
checked or unchecked.
Here is a workaround, you can handle the mouse click event in the TreeView, detect the change of the TreeView SelectedNodes.
int oldCount = 0;
public MainPage()
{
this.InitializeComponent();
DessertTree.AddHandler(PointerReleasedEvent, new PointerEventHandler(TreeView_OnPointerReleased), true);
}
private void TreeView_OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
if (oldCount != DessertTree.SelectedNodes.Count)
{
foreach (TreeViewNode node in DessertTree.SelectedNodes)
{
do somthing...
}
oldCount = DessertTree.SelectedNodes.Count;
}
}
I need to access the scrollviewer of a listview from the codebehind.
here is the definition of my listview
<ListView Grid.Row="1" ItemsSource="{Binding Path=SpecList, UpdateSourceTrigger=PropertyChanged}"
Name="mylistview"
ItemTemplate="{StaticResource SpecElementTemplate}"
Background="{StaticResource EnvLayout}"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ItemContainerStyle="{StaticResource MyStyle}"
BorderBrush="Blue"
BorderThickness="20"
Margin="-2">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
How can I get the scrollviewer?
Thank you
Andrea
There are several ways to get the ScrollViewer. Simplest solution is to get the the first child of the first child of the ListView. This means get the Border and the ScrollViewer inside this Border like described in
this answer:
// Get the border of the listview (first child of a listview)
Decorator border = VisualTreeHelper.GetChild(mylistview, 0) as Decorator;
// Get scrollviewer
ScrollViewer scrollViewer = border.Child as ScrollViewer;
A second way is to scan all childrens recursive to find the ScrollViewer. This is described in the answer by Matt Hamilton in this question. You can simply use this function to get the ScrollViewer.
ScrollViewer scrollViewer = GetChildOfType<ScrollViewer>(mylistview);
This second solution is much more generic and will also work if the template of your ListView was edited.
Use VisualTreeHelper class to access any child control.
Psudeo code to your case:
//Declare a scroll viewer object.
ScrollViewer sViewer = default(ScrollViewer );
//Start looping the child controls of your listview.
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(YOUR_LISTVIEW_OBJECT.VisualParent ); i++)
{
// Retrieve child visual at specified index value.
Visual childVisual = (Visual)VisualTreeHelper.GetChild(YOUR_LISTVIEW_OBJECT.VisualParent , i);
ScrollViewer sViewer = childVisual as ScrollViewer;
//You got your scroll viewer. Stop looping.
if (sViewer != null)
{
break;
}
}
I also suggest using the CollectionChanged event. In this code, the CollectionChanged event handler is added to the codebehind after the view model has been loaded. Then, each time the collection changes we scroll to the bottom of the listview. Here is an important point. The scrollviewer child of the list view might not yet be completely rendered when our events start firing. Hence we will get exceptions if we try to use the VisualTreeHelper.GetChild method. So, we have to first attempt to get the scrollviewer and then ignore its positioning if it is not yet available.
private void ReceivedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Make sure the items source property in the viewmodel has some items
if (myViewModel.ReceivedItems.Count > 0)
{
var aScrollViewer = RcvdListView.GetChildOfType<ScrollViewer>();
// Make sure the scrollviewer exists before trying to position it
if (aScrollViewer != null)
{
aScrollViewer.ScrollToBottom();
}
}
}
Listview's ScrollViewer should be accessible after LayoutUpdated. You could hook on LayoutUpdated and then get if from Visual tree
private static void ListView_LayoutUpdated(object sender, EventArgs e)
{
var listview = (ListView)sender;
var viewer = listview.GetFirstChildOfType<ScrollViewer>();
}
public static T GetFirstChildOfType<T>(this DependencyObject dependencyObject) where T : DependencyObject
{
if (dependencyObject == null)
{
return null;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
var result = (child as T) ?? GetFirstChildOfType<T>(child);
if (result != null)
{
return result;
}
}
return null;
}
I've a collection of items inside an ObservableCollection, each item have a specific nation name (that's only a string). This is my collection:
private ObservableCollection<League> _leagues = new ObservableCollection<League>();
public ObservableCollection<League> Leagues
{
get
{
return _leagues;
}
set
{
_leagues = value;
OnPropertyChanged();
}
}
the League model have only a Name and a NationName properties.
The Xaml looks like this:
<Controls:DropDownButton Content="Leagues" x:Name="LeagueMenu"
ItemsSource="{Binding Leagues}"
ItemTemplate="{StaticResource CombinedTemplate}" >
<Controls:DropDownButton.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding NationName}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</Controls:DropDownButton.GroupStyle>
</Controls:DropDownButton>
but I doesn't get any header for the NationName property, the items inside the DropDown are organized without header but as list, so without organization.
I'm trying to get this predisposition.
What am I doing wrong?
Preliminaries
Grouping items in an ItemsControl in WPF (which DropDownButton derives from) is fairly simple, and is accomplished in two steps. First you need to set up the items source by tweaking an ICollectionView associated with the source collection. Then you need to populate the ItemsControl.GroupStyle collection with at least one GroupStyle item - otherwise the items are presented in a plain (non-grouped) manner.
Diagnosis
The main issue you're facing is getting the drop-down to present the items in a grouped manner. Unfortunately, unlike setting up the items source, it is not something that is easily accomplished in case of the DropDownButton control. The reason for that stems from the way the control (or, more precisely, its template) is designed - the drop-down is presented inside a ContextMenu attached to a Button which is part of the template (see MahApps.Metro source code). Now ContextMenu also derives from ItemsControl, and most of its properties are bound to corresponding properties of the templated DropDownButton. That is however not the case for its GroupStyle property, because it's a read-only non-dependency property, and cannot be bound or event styled. That means that even if you add items to DropDownButton.GroupStyle collection, the ContextMenu.GroupStyle collection remains empty, hence the items are presented in non-grouped manner.
Solution (workaround)
The most reliable, yet most cumbersome solution would be to re-template the control and add GroupStyle items directly to the ContextMenu.GroupStyle collection. But I can offer you a much more concise workaround.
First of all, let's deal with the first step - setting up the items source. The easiest way (in my opinion) is to use CollectionViewSource in XAML. In your case it would boil down to something along these lines:
<mah:DropDownButton>
<mah:DropDownButton.Resources>
<CollectionViewSource x:Key="LeaguesViewSource" Source="{Binding Leagues}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="NationName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</mah:DropDownButton.Resources>
<mah:DropDownButton.ItemsSource>
<Binding Source="{StaticResource LeaguesViewSource}" />
</mah:DropDownButton.ItemsSource>
</mah:DropDownButton>
Now for the main part - the idea is that we'll create a helper class that will contain one attached dependency property that will assign an owner DropDownButton control to the ContextMenu responsible for presenting its items. Upon changing the owner we'll observe its DropDownButton.GroupStyle collection and use ContextMenu.GroupStyleSelector to feed the ContextMenu with items coming from its owner's collection. Here's the code:
public static class DropDownButtonHelper
{
public static readonly DependencyProperty OwnerProperty =
DependencyProperty.RegisterAttached("Owner", typeof(DropDownButton), typeof(DropDownButtonHelper), new PropertyMetadata(OwnerChanged));
public static DropDownButton GetOwner(ContextMenu menu)
{
return (DropDownButton)menu.GetValue(OwnerProperty);
}
public static void SetOwner(ContextMenu menu, DropDownButton value)
{
menu.SetValue(OwnerProperty, value);
}
private static void OwnerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var menu = (ContextMenu)d;
if (e.OldValue != null)
//unsubscribe from the old owner
((DropDownButton)e.OldValue).GroupStyle.CollectionChanged -= menu.OwnerGroupStyleChanged;
if (e.NewValue != null)
{
var button = (DropDownButton)e.NewValue;
//subscribe to new owner
button.GroupStyle.CollectionChanged += menu.OwnerGroupStyleChanged;
menu.GroupStyleSelector = button.SelectGroupStyle;
}
else
menu.GroupStyleSelector = null;
}
private static void OwnerGroupStyleChanged(this ContextMenu menu, object sender, NotifyCollectionChangedEventArgs e)
{
//this method is invoked whenever owners GroupStyle collection is modified,
//so we need to update the GroupStyleSelector
menu.GroupStyleSelector = GetOwner(menu).SelectGroupStyle;
}
private static GroupStyle SelectGroupStyle(this DropDownButton button, CollectionViewGroup group, int level)
{
//we select a proper GroupStyle from the owner's GroupStyle collection
var index = Math.Min(level, button.GroupStyle.Count - 1);
return button.GroupStyle.Any() ? button.GroupStyle[index] : null;
}
}
In order to complete the second step we need to bind the Owner property for the ContextMenu (we'll use DropDownButton.MenuStyle to do that) and add some GroupStyle items to the DropDownButton:
<mah:DropDownButton>
<mah:DropDownButton.MenuStyle>
<Style TargetType="ContextMenu" BasedOn="{StaticResource {x:Type ContextMenu}}">
<Setter Property="local:DropDownButtonHelper.Owner" Value="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
</mah:DropDownButton.MenuStyle>
<mah:DropDownButton.GroupStyle>
<GroupStyle />
</mah:DropDownButton.GroupStyle>
</mah:DropDownButton>
This I think should be enough to achieve your goal.
If you check out the other post you've linked to, the answer has it all - in particular you need to bind to a CollectionView, rather than directly to the collection. Then you can set up grouping on the CollectionView.
So, in your case, define the property:
public ICollectionView LeaguesView { get; private set; }
and then after you've created your Leagues Collection, attach the View to your collection, and while you're at it set up the grouping on the view:
LeaguesView = (ListCollectionView)CollectionViewSource.GetDefaultView(Leagues);
LeaguesView.GroupDesriptions.Add(new PropertyGroupDescription("NationName"));
Then, bind your DropDownButton ItemSource to LeaguesView, and change your HeaderTemplate to bind to "Name" - which is the the name of the group:
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
You can also use the ItemCount property in there if you want to show how many items there are in the group.
I am building a WPF application using C# and also i used MVVM architecture in my application.
I created a CheckBox column in telerik gridview by using DataTemplate. I am using a collection to bind the data in the GridView .
How can i find the particular row number of DataItem has been selected in that Collection When CheckBox is checked on the Grid.
Here My code for creating CheckBox on Grid is:
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<CheckBox Name="StockCheckBox" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type telerik:GridViewRow}}, Path=IsSelected}" />
</StackPanel>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
And My Collection is,
foreach (var AvailableStock in AvailableStocks)// In this **AvailableStocks**(IEnumurable Collection) I got all the datas in the Gridview
//In this collection How can i know that the particular RowItem is selected in that gridview by CheckBox
{
if (SelectedStock != null)
{
this.SelectedStocks.Add(AvailableStock );
this.RaisePropertyChanged(Member.Of(() => AvailableStocks));
}
}
Anyone Please tell me some suggestion on this How can i achieve this? How can i identify that particular row has been selected?
Thanks in Advance.
I would recommend using an MVVM approach and use a Binding to get the selected items. Unfortunately, the DataGrid doesn't provide a DependencyProperty for selected items, but you can provide your own. Derive a class from DataGrid, register a dependency property for SelectedItems and override the SelectionChanged event to update your dependency property. You can then use a Binding to inform your ViewModel of the selected items.
Code:
public class CustomDataGrid : DataGrid
{
public static readonly DependencyProperty CustomSelectedItemsProperty = DependencyProperty.Register(
"CustomSelectedItems", typeof (List<object>), typeof (CustomDataGrid),
new PropertyMetadata(new List<object>()));
public List<object> CustomSelectedItems
{
get { return (List<object>) GetValue(CustomSelectedItemsProperty); }
set { SetValue(CustomSelectedItemsProperty, value);}
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
foreach(var item in e.AddedItems)
CustomSelectedItems.Add(item);
foreach (var item in e.RemovedItems)
CustomSelectedItems.Remove(item);
base.OnSelectionChanged(e);
}
}
XAML:
<Grid>
<Ctrl:CustomDataGrid CustomSelectedItems="{Binding MySelectedItems}"/>
</Grid>
How to get next sibling of the element in visual tree? This elment is dataitem of the databound ItemsSource. My goal is get acces to sibling in code (assume that i have access to the element itself) and then use BringIntoView.
Thanks.
For example, if your ItemsControl is a ListBox, the elements will be ListBoxItem objects. If you have one ListBoxItem and you want the next ListBoxItem in the list, you can use the ItemContainerGenerator API to find it like this:
public static DependencyObject GetNextSibling(ItemsControl itemsControl, DependencyObject sibling)
{
var n = itemsControl.Items.Count;
var foundSibling = false;
for (int i = 0; i < n; i++)
{
var child = itemsControl.ItemContainerGenerator.ContainerFromIndex(i);
if (foundSibling)
return child;
if (child == sibling)
foundSibling = true;
}
return null;
}
Here's some sample XAML:
<Grid>
<ListBox Name="listBox">
<ListBoxItem Name="item1">Item1</ListBoxItem>
<ListBoxItem Name="item2">Item2</ListBoxItem>
</ListBox>
</Grid>
and the code-behind:
void Window_Loaded(object sender, RoutedEventArgs e)
{
var itemsControl = listBox;
var sibling = item1;
var nextSibling = GetNextSibling(itemsControl, sibling) as ListBoxItem;
MessageBox.Show(string.Format("Sibling is {0}", nextSibling.Content));
}
which results in:
This also works if the ItemsControl is data-bound. If you only have the data item (not the corresponding user interface element), you can use the ItemContainerGenerator.ContainerFromItem API to get the initial sibling.