How to get CommandBar from XAML resources in C++/CX? - xaml

I defined CommandBar in the page resources:
<Page.Resources>
<CommandBar x:Name="mainCommandBar" x:Key="mainCommandBar">
<AppBarButton Icon="Accept" Label="AppBarButton"/>
<AppBarButton Icon="Cancel" Label="AppBarButton"/>
</CommandBar>
</Page.Resources>
And then in the page constructor I assign this commandBar to TopAppBar:
TopAppBar = mainCommandBar;
But I get an error "Element not found".
If I create CommandBar programmatically - all works.

You need to remove the entry in the ResourceDictionary prior to assigning it to TopAppBar because an instance of Control may not appear multiple times in XAML tree.
MainPage::MainPage()
{
InitializeComponent();
Resources->Remove(L"mainCommandBar");
TopAppBar = mainCommandBar;
}

First you need to retrieve the CommandBar Style resource out of a page’s resource dictionary and then assign this commandBar to TopAppBar.
MainPage::MainPage()
{
InitializeComponent();
CommandBar^ commandBarStyle = (CommandBar^)this->Resources->Lookup("mainCommandBar");
CommandBar^ TopAppBar = commandBarStyle;
}

Related

How to mix dynamic and static items in UWP XAML NavigationView MenuItems?

I'm trying to make a NavigationViewMenu and I need a menu layed out as follows
static Home item
static Header
dynamic elements from DB as items
static Header
static set of items
This is what I tried:
<NavigationView.MenuItems>
<NavigationViewItem Icon="Home" Content="Home" Tag="home" />
<NavigationViewItemSeparator />
<NavigationViewItemHeader Content="My Stuff"/>
<NavigationViewList ItemsSource="{x:Bind MyStuff}">
<NavigationViewList.ItemTemplate>
<DataTemplate x:DataType="local:MyModel">
<NavigationViewItem Icon="Pictures" Content="{x:Bind Name}" Tag="{x:Bind Tag}" />
</DataTemplate>
</NavigationViewList.ItemTemplate>
</NavigationViewList>
<!-- Static equivalent to the above:
<NavigationViewItem Icon="Pictures" Content="Woop" Tag="foos"/>
<NavigationViewItem Icon="Pictures" Content="Doop" Tag="foos"/>
<NavigationViewItem Icon="Pictures" Content="Loop" Tag="foos"/>
-->
<NavigationViewItemHeader Content="Other Stuff"/>
<NavigationViewItem Icon="Pictures" Content="Foos" Tag="foos"/>
<NavigationViewItem Icon="ContactInfo" Content="Bars" Tag="bars"/>
<NavigationViewItem Icon="SwitchApps" Content="Bazes" Tag="bazes"/>
</NavigationView.MenuItems>
This is what I've got:
This is what I wanted:
Is there anything as good and practical as Angular's *ngFor in XAML for UWP?
I ran into the same behavior, and managed to find a work around. In my case, I had two lists of menu items (dynamically data-bound items), and I wanted to use NavigationViewItemHeader on top of both (static items). I tried using a NavigationViewList and ran into your problem.
TL;DR:
Create a list of menu items in C# code. The elements of this list can be a mix of your viewmodels, and any static Navigation Items (headers, separators, etc). Then use a DataTemplateSelector to either databind to your viewmodel or pass-through the navigation items unchanged.
More detailed
In your C# code-behind, create an enumerable (or observable collection) of your menu items. In my case SomeCollection and AnotherCollection represent my data sources that I wanted to bind to my NavigationView. I have to type it as object because it's a mix of my viewmodels and the built-in UWP navigation item types.
private IEnumerable<object> MenuItems()
{
yield return new NavigationViewItemHeader { Content = "Some List" };
foreach (var some in SomeCollection)
{
yield return some;
}
yield return new NavigationViewItemHeader { Content = "Another List" };
foreach (var another in AnotherCollection)
{
yield return another;
}
}
// somewhere else, like in your Page constructor or a CollectionChanged handler
this.NavigationList = MenuItems().ToList();
Second, create a Data Template Selector to switch between your template and the navigation items:
class NavigationItemTemplateSelector : DataTemplateSelector
{
public DataTemplate ViewModelTemplate{ get; set; }
public DataTemplate NavigationItemTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
return item is MyViewModel
? ViewModelTemplate
: NavigationItemTemplate;
}
}
Finally, change your NavigationView to reference the template selector and menu item source. The NavigationItemTemplate is just a pass-through, and your ViewModelTemplate would have the normal viewmodel item binding logic.
<Page.Resources>
<DataTemplate x:Key="ViewModelTemplate" x:DataType="local:MyViewModel">
<TextBlock Text="{x:Bind SomeProperty}" />
</DataTemplate>
<DataTemplate x:Key="NavigationItemTemplate">
</DataTemplate>
<local:NavigationItemTemplateSelector x:Key="NavigationItemTemplateSelector"
ViewModelTemplate="{StaticResource ViewModelTemplate}"
NavigationItemTemplate="{StaticResource NavigationItemTemplate}" />
</Page.Resources>
<NavigationView
MenuItemsSource="{x:Bind NavigationList, Mode=OneWay}"
MenuItemTemplateSelector="{StaticResource NavigationItemTemplateSelector}">
<Frame x:Name="ContentFrame"></Frame>
</NavigationView>
I can reproduce it. It looks like NavigationViewList only take the space of one item when putting itself in NavigationView.MenuItem. Which is the same like putting a ListView in a ListViewItem. To change this behavior we need to change the item's behaviour ourselves. However after some investigating it seems currently customization of NavigationViewList is blackbox for us. So the only way I could think is to build our own NavigationView with the help of splitview and acrylic.
I didn't find it necessary to use different templates as in the accepted answer, maybe because there were some changes in the underlying Windows code in the meantime. As I needed a stable part of the menu and then a dynamic part depending on the actual page, I created an interface:
interface IMenuProvider {
IEnumerable<NavigationViewItemBase> GetMenuItems();
}
and made sure all my pages implement it. My MainPage returns the fixed part:
public IEnumerable<NavigationViewItemBase> GetMenuItems() {
yield return new NavigationViewItem {
Tag = "home",
Icon = new SymbolIcon(Symbol.Home),
Content = "Home",
};
yield return new NavigationViewItemSeparator();
yield return new NavigationViewItem {
Tag = "xxx",
Icon = new SymbolIcon(Symbol.XXX),
Content = "XXX",
};
}
the other pages, similary, provide their own menu headers and items.
When I navigate the pages, I change the menu as well, concatenating the fixed and variable parts:
ContentFrame.Navigate(PageType, null, transitionInfo);
if (ContentFrame.Content is IMenuProvider menuProvider)
= GetMenuItems().Concat(menuProvider.GetMenuItems()).ToList();
(Or, you might place the menu change into the Navigated handler of the Frame.)
While it's still a nuisance that these menus, at least the fixed part, cannot be declared in XAML, this approach works.

Maps elements display as text (class name)

I use WinRt MapControl on Windows Phone 8.1. But when I trying add MapIcon or MapPolyline map elements I get only text like this:
XAML code looks like this:
<maps:MapControl x:Name="MapOnScreenControl"
MapServiceToken="12345">
<maps:MapPolyline Path="{Binding Route, Converter={StaticResource RouteToGeopathConverter}}"/>
</maps:MapControl>
What I am doing is wrong? Thanks.
Unfortunately map elements cannot be added to the map via XAML. You will need to add them within code.
MapOnScreenControl.MapElements.Add(new MapPolyline());
A trick I like to do is use the viewmodel to add elements to the map by either setting a Map property or a MapElements property of my viewmodel.
private void MapPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
var vm = DataContext as MapViewModel;
vm.MapElements = MyMap.MapElements;
}
Then within the viewmodel you can add items to the elements.
You can also add a collection of items using the MapItemsControl.
<maps:MapControl x:Name="Map" MapServiceToken="abcdef-abcdefghijklmno">
<maps:MapItemsControl ItemsSource="{Binding Locations}">
<maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="Assets/Mappin.png" Height="25"
maps:MapControl.NormalizedAnchorPoint="1,0.5"
maps:MapControl.Location="{Binding Geopoint}" />
</DataTemplate>
</maps:MapItemsControl.ItemTemplate>
</maps:MapItemsControl>
</maps:MapControl>

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.

Implement IE/Microsoft Edge bottom bar in XAML

I am a huge fan of the browser on Windows phone and I want to port a similar bottom bar to my app. Right now, I am using a standard CommandBar.
<Page.BottomAppBar>
<CommandBar>
<AppBarButton Icon="Go" Click="Go"/>
<CommandBar.SecondaryCommands>
<AppBarButton Icon="Setting" Label="Settings" Click="ShowSettings"/>
</CommandBar.SecondaryCommands>
</CommandBar>
</Page.BottomAppBar>
As this wastes screen space, I really want to make use of the remaining space of the bar to add something like app status (in place of the address bar of Edge/IE), something like download/upload progress. Unfortunately, the CommandBar does not allow introducing things like TextBlock or ProgressRing. To make use of those controls, we need to change to an AppBar instead. But then, I cannot use the features of CommandBar like the adding 3 dots buttons to open up the hidden buttons.
Is there an easy way to achieve this i.e. combining the flexibility of AppBar and the 3-dot feature of CommandBar?
CommandBar only accept the control that inherit ICommandBarElement interface.
We can create one UserControl which inherit ICommandBarElement, simply did a small test without optimize the code, take a look to see if it helps:
public sealed partial class MyUserControl1 : UserControl, ICommandBarElement
{
public MyUserControl1()
{
this.InitializeComponent();
}
private bool _IsCompact = true;
bool ICommandBarElement.IsCompact
{
get
{
return _IsCompact;
}
set
{
_IsCompact = value;
}
}
}
Also the UserControl XAML:
<UserControl
x:Class="App10.MyUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App10"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="400" Height="38.027">
<Grid>
<TextBlock Foreground="DarkBlue">asdsadasdasdasdasda</TextBlock>
</Grid>
And then we use the userControl in the CommandBar, here we go:
http://i.stack.imgur.com/Bgug9.png
Note: please further optimize it for instance register some Text dependency properties to enable accept the data binding.
Per the documentation on MSDN you can use the CommandBar.Content property which corresponds to the empty area to the side of any primary commands. To alter the alignment of the content you'd need to change the CommandBar.HorizontalContentAlignment property.
<CommandBar HorizontalContentAlignment="Stretch">
<AppBarButton Icon="Go" Click="Go"/>
<CommandBar.SecondaryCommands>
<AppBarButton Icon="Setting" Label="Settings" Click="ShowSettings"/>
</CommandBar.SecondaryCommands>
<CommandBar.Content>
<Grid>
<TextBox HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Margin="12,8"/>
</Grid>
</CommandBar.Content>
</CommandBar>
And as of Win10 the recommendation is to place the CommandBar inline instead of using the Page.TopAppBar or Page.BottomAppBar properties. The one scenario in which you may still want to use the Page.BottomAppBar property is to ensure the CommandBar remains visible when the software keyboard appears.

Binding the ZIndex of items in an ItemsControl

Using Caliburn.Micro for a WinRT application, I would like to control the ZIndex of items displays in an ItemsControl.
When a user taps on an item, it should become the topmost element.
<ItemsControl Background="White" Height="auto" Width="auto" x:Name="Parts"
HorizontalAlignment="Left"
VerticalAlignment="Top"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The viewmodel bound to the view above contains a property Parts:
private BindableCollection<IPartViewModel> _parts = new BindableCollection<IPartViewModel>();
public BindableCollection<IPartViewModel> Parts
{
get { return _parts; }
set { _parts = value; NotifyOfPropertyChange(() => Parts); }
}
IPartViewModel has different implementations, each with their own View (= custom user controls).
Every implementation of IPartViewModel has a ZIndex property, ready to be bound.
All other bindings (labels, the tapped event, ...) work perfectly, but I cannot figure out what the binding should look like to control the ZIndex.
Many other questions on SO deal with this issue, but none for WinRT.
I ended up achieving this with a custom ItemsControl and overriding the method GetContainerForItemOverride where I return a ContentPresenter with a binding added to the ZIndex property
protected override DependencyObject GetContainerForItemOverride()
{
var cp = new ContentPresenter();
cp.SetBinding(Canvas.ZIndexProperty, new Binding { Path = new PropertyPath("ZIndex") });
return cp;
}