Sending LoadingPivotItem event to PivotItems (UserControls) - xaml

So, i have Pivot and PivotItems as UserControls.
I'd like to know, when every PivotItem is NavigatedTo and NavigatedFrom.
I made a base class (PivotItems are inheriting it), added there 2 methods (To and From), and i have LoadingPivotItemCommand() in the pivot, so i know, which PivotItem is loaded.
But how to broadcast this event to pivots? I tried some ways, but all of them are nulls.
void LoadingPivotItemCommand(PivotItemEventArgs args)
{
var b = args.Item.Parent as BaseUserControl;
var a = args.Item.Content as BaseUserControl;
var a1 = args.Item.Content as UserControl;
var c = args.Item.DataContext as BaseUserControl;
if (c != null)
c.OnPivotItemActivated();
}
PivotItems are defined in xaml:
<controls:PivotItem Header="{Binding Path=MainResources.Products, Source={StaticResource LocalizedStrings}, Converter={StaticResource StringToLowerCaseConverter}}"
Name="PivotItemProducts">
<Grid>
<productsView:ProductUserControl />
</Grid>
</controls:PivotItem>

I'm guessing ProductUserControl is the one inheriting form BaseUserControl. If your BaseUserControl is always inside a Grid like in the xaml you show then you could just use :
var a1 = (args.Item.Content as Grid).Children[0] as BaseUserControl;
Otherwise if your UserControl can be place at different part inside the PivotItem then you can just use the function that I gave you, just replacing Image by BaseUserControl.

Related

CompositeCollection containing an ICollectionView

I'm trying to implement a tab control, where each item comes from an ICollectionView of my viewmodel. Each tab page, for the items from the ICollectionView will be the same. However, I would like there to be an extra tab page for configuration options.
So an example tab header 'screenshot' might be:
tabA | tabB | tabC | config
on another instance, it could be
tabA | config
or
config
I can define the header for each item using ItemTemplateSelectors, and the content using the ContentTemplateSelectors. So that bit should be okay.
I'm having trouble with adding the config page item since I do not know where to add it. I thought I could set the tab's ItemsSource to be a CompositeCollection, where the final item is the config page object. I have failed to achieve this.
In the following example, I can view the tab headers being populated correctly according to the designer sample data which I have set up - I have not yet added the config page.
<controls:MetroTabControl ItemsSource="{Binding View}">
<controls:MetroTabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value.siteDisplayName}" />
</DataTemplate>
</controls:MetroTabControl.ItemTemplate>
<controls:MetroTabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value.siteComment}"/>
</DataTemplate>
</controls:MetroTabControl.ContentTemplate>
</controls:MetroTabControl>
As you see, I have set the ItemsSource to be {Binding View}. This "View" comes from my ViewModel and is an ICollectionView.
Ideally i'd be able to do some magic like:
<controls:MetroTabControl>
<controls:MetroTabControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding View}"/>
<SomeConfigPageObject/>
</CompositeCollection>
</controls:MetroTabControl.ItemsSource>
...snip...
</controls:MetroTabControl>
But the problem is that when I do the above, the designer preview of the control acts as if there are no items in the ItemsSource.
For reference, each item in the {Binding View} is a object which contains a Value property, the value property containing an object that contains, in this example, a siteDisplayName and siteComment.
For reference, the DataContext for the tab is defined the dockpanel that contains it, as follows.
<DockPanel DataContext="{Binding Source={StaticResource Configurator}}"
d:DataContext="{d:DesignInstance cfuid:ConfigSiteVMSampleData, IsDesignTimeCreatable=true}"
LastChildFill="True">
For reference, the Configurator is my viewmodel and is instantiated in the xaml as:
<UserControl.Resources>
<ResourceDictionary>
...snip...
<cfvmc:ConfigSiteVM x:Key="Configurator" />
...snip...
So, the actual question would be:
How do I add my "config page" at the end of the tab control? Preferably via using the above-hoped method of adding an extra config-page object on the CompositeCollection; however if this is not possible [1] i'm open for suggestions.
[1] I think it doesn't work because the {Binding View} is an ICollectionView and the CompositeCollection requires a "collection" and doesn't accept a "view"
Thank you.
Peter.
I decided to do it through code behind. This means that I do lose my ability to use the design-time data to preview my UI; but it works at run time.
So, in the xaml I have.
<controls:MetroTabControl Grid.Column="0" Grid.ColumnSpan="2"
Grid.Row="0" Grid.RowSpan="2"
ItemsSource="{Binding ElementName=ucMe, Path=TabSitesCollection}">
Where ucMe is the UserControl and TabSitesCollection is a
protected CollectionViewSource m_TabSitesCollectionViewSource;
protected CompositeCollection m_TabSitesComposites;
public ICollectionView TabSitesCollection
{
get { return m_TabSitesCollectionViewSource.View; }
}
That gets initialised in the constructor as follows
public ConfigSiteView()
{
m_TabSitesComposites = new CompositeCollection();
m_TabSitesCollectionViewSource = new CollectionViewSource();
m_TabSitesCollectionViewSource.Source = m_TabSitesComposites;
InitializeComponent();
}
Then, on the Loaded event I can do
m_TabSitesComposites.Add(new CollectionContainer() { Collection = GetModel.View });
m_TabSitesComposites.Add(new TabItem() { Header = "hi" });
m_TabSitesComposites.Add(new TabItem() { Header = "ho" });
This results in almost my desired UI
I now simply need to spiff up my settings tab item and i'm done.
For reference, the xaml designer does not have any preview data - Unless I change the xaml so that the preview loads up (which then breaks the actual execution)
It would have been nice to have it both work while running, and on preview, but I haven't figured out how to do that, and it's not a current priority.

Binding to xaml page's DataContext in Windows Universal App

I'm using a content dialog do display instance data when an item in a grid is selected.
In the calling page's view model, when an item is selected the following method is executed.
public virtual void ItemSelected(object sender, object parameter)
{
var arg = parameter as Windows.UI.Xaml.Controls.ItemClickEventArgs;
var clickedItem = arg.ClickedItem;
var item = clickedItem as ItemsModel;
var dialog = new ItemsDialog();
dialog.DataContext = item;
dialog.ShowAsync();
}
This shows the dialog, and the content is displayed as expected. Now I'm trying to split my xaml into different templates and I'm trying to use a ContentControl to display the appropriate template. I've written a DataTemplateSelector to help choose the correct template, but now I cannot figure out the data binding for the ContentControl (see simplified version below).
<ContentDialog.Resources>
<UI:MyTemplateSelector x:Key="MyTemplateSelector"
Template1="{StaticResource Template1}"
Template2="{StaticResource Template2}"/>
<DataTemplate x:Key="Template1"/>
<DataTemplate x:Key="Template2"/>
</ContentDialog.Resources>
<StackPanel>
<ContentControl DataContext="{Binding}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}"/>
</StackPanel>
When debugging into my ContentTemplateSelector, my binding is always null. I've tried various forms of the binding syntax with no luck. How do I properly set the DataContext of the ContentControl to that of the ContentDialog?
When debugging into my ContentTemplateSelector, my binding is always
null
You need to set data binding for the Content property of ContentControl control, see Remarks in MSDN:
The Content property of a ContentControl can be any type of object,
such as a string, a UIElement, or a DateTime. By default, when the
Content property is set to a UIElement, the UIElement is displayed in
the ContentControl. When Content is set to another type of object, a
string representation of the object is displayed in the
ContentControl.
So the following xaml should work:
<StackPanel>
<ContentControl Content="{Binding}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}"/>
</StackPanel>
Check my completed sample in Github
You have to bind Content also.
Content="{Binding}"
You have the data source (DataContext) and how the data is displayed (templates) and now you need to specify which of the properties brings that together.

Numbering a ListView

I have a ListView whose Data Context is an ObservableCollection. I then used XAML to format the items and bind to their properties in the ListView in ListView.ItemTemplate.DataTemplate . Now, I want to add a TextBlock here to display the position of the item in the ListView through Binding in XAML. How to do that?
If I got your question right, you just need to add a TextBlock in each item DataTemplate with the index number inside the ListView, right?
If so, you can easily do that by doing something like this:
<DataTemplate x:Key="ItemTemplate">
<Grid>
<!--All your various UI elements here...-->
<!--Add a TextBlock with the formatting you want-->
<TextBlock Text="{Binding Position}"/>
</Grid>
</DataTemplate>
And in C#, add something like this after you've created your Source ObservableCollection, before assigning it to the Property inside your ViewModel:
for (int i = 0; i < myCollection.Count; i++)
{
myCollection[i].Position = Convert.ToString(i);
}
//And then, the usual
Source = myCollection
NB: I'm assuming you have a Property called Source in your ViewModel with INotifyPropertyChanged implemented. You will also have to modify the class you use inside your ObservableCollection and add the Position property of course :)

CollectionViewSource "Value does not fall within the expected range."

Why does this code produce the error in a Windows 8 XAML application?
Value does not fall within the expected range.
The XAML:
<SemanticZoom>
<SemanticZoom.ZoomedInView>
<ListView
Style="{StaticResource HorizontalListViewStyle}"
SelectionMode="None"
ScrollViewer.IsHorizontalScrollChainingEnabled="False"
ItemsSource="{Binding BoardItems}"
ItemContainerStyle="{StaticResource ZoomedOutListViewItemContainerStyle}"
...
The MVVM code:
ObservableCollection<WritingBoardModel> boards = new ObservableCollection<WritingBoardModel>();
... // Add item models to boards.
CollectionViewSource v = new CollectionViewSource()
{
Source = boards,
};
this.ViewModel.Add(BoardItemsViewModelKey, v);
If I skip the CollectionViewSource and directly add the boards instance to my view model, then it all works.
I think I need to use a CollectionViewSource in order to get some semantic zoom selection behaviour to work.
So, CollectionViewSources are weird and the way you have to bind to them is weird as well. To give you an example, in order to do it 'by the book' (the way the sample projects do), I've found it practically has to be a StaticResource as such:
<Page.Resource>
<CollectionViewSource Source="{Binding Whatev}"
x:Key="WhatevSource"/>
</Page.Resource>
<GridView ItemsSource="{Binding Source={StaticResource WhatevSource}}"/>
Notice that we're not setting the source directly to the CollectionViewSource, but we're setting a 'pathless' Binding, basically using the CollectionViewSource as a DataContext (just one way to think of it, not actually technically correct).
This is the only way I've been able to get it to work, though I believe you can technically in the codebehind set the ItemsSource directly to the View of the CollectionViewSource or something similar.
In your Listview add "StaticResource" and "Source"
<ListView ItemsSource="{Binding Source={StaticResource WhatevSource}}"/>
I needed to bind to the View property of the CollectionViewSource like this:
CollectionViewSource v = new CollectionViewSource()
{
IsSourceGrouped = false,
Source = boards,
};
this.ViewModel.Add(BoardItemsViewModelKey, v.View);
Mind you, this doesn't help with my two ListViews and keeping them in selection synch in a SemanticZoom.

XAML/C#: What event fires after reordering a gridview?

I am making my first Windows Store app in Visual Studios 2012. I have a gridview control that I have enabled to be reordered. I have code that I need to run when the list is reordered. I have tried the Drop event. It does not fire. I tried several other drag events, which also did not fire. It seems like this should be so simple... Thanks for your time!
You cannot reorder a GridView unless the ItemsSource is bound to an ObservableCollection and CanReorderItems, CanDragItems, and AllowDrop are set to true. It is not necessary to use a CollectionViewSource to enable reordering in your gridview. In fact, a collectionviewsource is often used for grouping a gridview and reordering is not possible when data is grouped.
Anyway, your XAML would look like this:
<Grid Background="Black">
<Grid.DataContext>
<local:MyModel/>
</Grid.DataContext>
<GridView CanReorderItems="True" CanDragItems="True" AllowDrop="True"
ItemsSource="{Binding Items}">
</GridView>
</Grid>
Although any enumerable can be bound to the ItemsSource of a GridView it is only an ObservableCollection that enables reorder. Yes, you can use a custom type that implements reorder, but why mess with that when ObservableCollection does it for you?
Your view model might look like this:
public class MyModel
{
public MyModel()
{
foreach (var item in Enumerable.Range(1, 50))
Items.Add(item);
Items.CollectionChanged += Items_CollectionChanged;
}
ObservableCollection<int> m_Items = new ObservableCollection<int>();
public ObservableCollection<int> Items { get { return m_Items; } }
object m_ReorderItem;
int m_ReorderIndexFrom;
void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
m_ReorderItem = e.OldItems[0];
m_ReorderIndexFrom = e.OldStartingIndex;
break;
case NotifyCollectionChangedAction.Add:
if (m_ReorderItem == null)
return;
var _ReorderIndexTo = e.NewStartingIndex;
HandleReorder(m_ReorderItem, m_ReorderIndexFrom, _ReorderIndexTo);
m_ReorderItem = null;
break;
}
}
void HandleReorder(object item, int indexFrom, int indexTo)
{
Debug.WriteLine("Reorder: {0}, From: {1}, To: {2}", item, indexFrom, indexTo);
}
}
In the code above, the reorder event is not real. It is derived from a combination of the "Remove" action and the "Add" action in the CollectionChanged event. Here's why this is awesome. If the reorder was only available from the GridView then the ViewModel would not be able to handle it. Because the underlying list is how you detect reorder, the ViewModel is enabled.
Every case is slightly different. You may not care about the Index so you can simplify the code. You may not allow adding or removing from the collection so you only need to monitor the Add action. Again, it depends on your situation. My sample code above should get 99% of the cases taken care of.
Remember, GridView is not the only control that allows reorder. Any control based on ListViewBase (like the ListView) supports reorder - still using ObservableCollection. But GridView is the most common control to use this feature. For sure.
Reference: http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.listviewbase.canreorderitems
Oh, to answer your question!
There is no event that indicates a reorder. Reorder is a derived action based on a combination of actions in the underlying ObservableCollection CollectionChanged event. Make sense?
By the way, here's sample syntax to bind to a CollectionViewSource, if you choose to:
<Grid Background="Black">
<Grid.DataContext>
<local:MyModel/>
</Grid.DataContext>
<Grid.Resources>
<CollectionViewSource x:Name="CVS" Source="{Binding Items}" />
</Grid.Resources>
<GridView CanReorderItems="True" CanDragItems="True" AllowDrop="True"
ItemsSource="{Binding Source={StaticResource CVS}}" >
</GridView>
</Grid>
Best of luck.