Navigation with ContentPresenter MVVM Xamarin Forms - c#

So I'm trying to make a simple navigation using MVVM in Xamarin forms, and people suggested that I used the control template with a content presenter.
So far so good.
I made the control template, but I'm not sure how to bind content presenter to my buttons so it changes when I click them.
App.Xaml
<Application.Resources>
<!-- Application resource dictionary -->
<ResourceDictionary>
<ControlTemplate x:Key="ThemeMaster">
<StackLayout>
<Label Text="App name" BackgroundColor="Blue"></Label>
<ContentPresenter x:Name="ContentPresenter"
Content="{Binding changeContentCommand}">
</ContentPresenter>
<Button Text="Click me" Command="{Binding changeContentButtonCommand}"></Button>
</StackLayout>
</ControlTemplate>
When I open the program contentpresenter starts showing the mainPage as it should, but what should I write in MainViewModel.cs too change contentpresenter too lets say LeaderBoardPage?

I think you should create different ControlTemplate for your different content.

Related

Xamarin Forms override Picker with custom ControlTemplate

I want to override the Xamarin Forms Control Picker so it display "No entries" if the binded collection is empty.
I tried:
<Picker
x:Class="v.App.Styling.Controls.vPicker"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:v.App.Styling.Controls;assembly=v.App"
x:Name="CustomPicker"
mc:Ignorable="d" >
<Picker.Style>
<Style TargetType="controls:vPicker">
<Setter Property="ControlTemplate">
<Setter.Value>
<ControlTemplate>
<controls:vStackLayout >
<ContentPresenter
IsVisible="{Binding ItemsSource.Count, Converter={StaticResource HasItemsToBoolConverter}, ConverterParameter=0}"
Content="{TemplateBinding Content}" />
<controls:vEntry
IsVisible="{Binding ItemsSource, Converter={StaticResource HasNoItemsToBoolConverter}"
IsEnabled="False"
TextColor="{StaticResource ColorTextSecondary}"
Text="No entries" />
</controls:vStackLayout>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Picker.Style>
</Picker>
But I get
Error XFC0001: Cannot resolve property "ControlTemplate" on type "vPicker (property missing or missing accessors)". (13, 21)
I'm not sure what I'm doing wrong...
Another solution that I offered someone trying to create their own style, but for a button. The underlying premise is the same. You have something special you want to do based on a default class (or derived).
I would take the picker control, subclass it and add your own dependency property boolean or visibility so it can be used within your own custom style template. A dependency property is instance specific, so if you had one picker that HAD entries, its flag would be different than another that had NO entries.
Now, that said,
Here is one source to start about your own templates / styles
But this one goes into an elaborate control template for a picker. You could build your own simplified and have your visible vs not context defined once as a style. Then when you define your combobox picker control, just have it reference the given style you declare. Having this style in a ResourceDictionary and available system wide can help all instances have the same look and feel without duplicating all over.
Update here
You could use a ContentView to write a custom control, as the picker doesn't have a ControlTemplate property. You could create your own custom picker view using ContentView, such like this:
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PickerControlTemplate74952715.PickerView"
x:Name="PickerView">
<ContentView.Content>
...
<controls:vStackLayout >
<ContentPresenter
IsVisible="{Binding ItemsSource.Count, Converter={StaticResource HasItemsToBoolConverter}, ConverterParameter=0}"
Content="{TemplateBinding Content}" />
<controls:vEntry
IsVisible="{Binding ItemsSource, Converter={StaticResource HasNoItemsToBoolConverter}"
IsEnabled="False"
TextColor="{StaticResource ColorTextSecondary}"
Text="No entries" />
</controls:vStackLayout>
</ContentView.Content>
For more info, you could refer to Xamarin.Forms control templates and Create a custom control
===============
you could use data bindings to make it.
In the .xaml for Picker:
<Picker x:Name="picker"
ItemsSource="{Binding ItemCollection}"
IsVisible="{Binding IsVisible}"
Title="Select a monkey"
TitleColor="Red">
</Picker>
And in ViewModel just add some logic like this:
public Command ClickedCommand // i define a button to add item to ItemCollection as a demo
{
get
{
return new Command(() =>
{
ItemCollection.Add(count);
count++;
if(ItemCollection.Count > 0)
{
IsVisible = true;
}
});
}
}
Hope it works for you.

ContentControl.Content setter always overwrites the content template content

I am fighting with this issue for at least a couple of hours already. I tried all possible solution I found on the net. My latest is below and you will see that I am trying to add a simple MenuBar to the main Window control and present the content beneath. My application is using MVVM and the view is assigned like this:
myMainWindow.Content = view; // this is what I cannot change
The soultion should be trivial but none works. I tried with ContentTemplate, ContentPresenter, Style setter, all the variations with binding but nothing works as expected that is whenever myMainWindow.Content menu bar disappears.
None of the samples available on the internet does not actaully show an application with MenuBar and content at the same time.
Is it so hard to add a menu bar in WPF application?
I would be more than happy for any new suggestions.
Thanks in advance.
<window:BaseWindow x:Class="OneTwoThree.Manager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:window="clr-namespace:Xsell.Client.Common;assembly=Xsell.Client.Common"
Title="{Binding Title}"
Width="1020"
Height="750"
Closed="AppClosed"
Icon="app.ico"
Loaded="AppLoaded">
<Window.Resources>
<Style TargetType="{x:Type Window}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Grid>
<DockPanel>
<Menu IsMainMenu="True" DockPanel.Dock="Top">
<MenuItem Header="Admin">
<MenuItem Header="Manage Labels"></MenuItem>
</MenuItem>
</Menu>
<ContentPresenter DockPanel.Dock="Bottom"/>
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
</window:BaseWindow>
Your application isn't really using MVVM. At best it might be sort of halfway using MVVM.
Meanwhile, all the XAML inside your <Window> tag in your main window XAML file is the window's Content. And you are explicitly replacing that with some random object, so of course it's not there any more.
You aren't giving much detail, but you need to assign your main viewmodel to the Window's DataContext and not assign anything to the window's Content.
You didn't say what view is in this line of code, so I have no idea what you were trying to do there:
myMainWindow.Content = view; // this is what I cannot change
Is view a viewmodel, or a view? Whatever it is, don't do that.
You could assign your main viewmodel to the Window's datacontext in XAML, too:
<Window ... >
<Window.DataContext>
<local:MyViewModel />
</Window.DataContext>
<!-- ... -->
</Window>
Then bind your viewmodel to the Content property of a ContentPresenter. There's no need to apply the window's content via a template here so I eliminated that part. The only template you need in this snipppet is the DataTemplate which displays your viewmodel in the ContentPresenter.
The layout here is copied verbatim from MainWindow.xaml in a project I recently wrote. I replaced my main menu with yours and omitted my Window.Resources. That's the only change.
<Window ...>
<DockPanel>
<Menu IsMainMenu="True" DockPanel.Dock="Top">
<MenuItem Header="Admin">
<MenuItem Header="Manage Labels"></MenuItem>
</MenuItem>
</Menu>
<Grid>
<ContentPresenter
Content="{Binding}"
/>
</Grid>
</DockPanel>
</Window>
A simple {Binding} with no Source or Path etc. properties will bind to the DataContext. The ContentPresenter will inherit its DataContext from the Window that contains it.
Then, if you've got a data template defined with a DataType the same as your main viewmodel, that data template will be instantiated in that ContentPresenter.
"Bind" doesn't mean "assign in code-behind". It means to use the System.Windows.Data.Binding class. The easiest way to do that is in the XAML as I've shown above.

How do I add a close button to a tab of the MahApps TabControl?

I was trying to follow this instruction on how to do it, but I'm just starting out with WPF.
How do I do this using a UserControl that i can reuse in different TabControls?
Also which one is the "Header" ContentPresenter in the TabControl Style?
Below is the instruction found at
https://github.com/MahApps/MahApps.Metro/issues/281
The other way is to modify/create a style - the issue then is hooking
it up to an actual 'close' event in a generic fashion.
If you look at the TabControl style, you'll see the "Header"
ContentPresenter. If you wrap that in a stackpanel and add a button
like so:
<StackPanel Orientation="Horizontal">
<Label x:Name="root" FontSize="26.67">
<ContentPresenter ContentSource="Header" RecognizesAccessKey="True" />
</Label>
<Button Content="X" />
</StackPanel>
You get :
If you have that in your Window or UserControl (rather than a resource
dictionary), you can wire that up so Click can fire and you can then
remove the item from the databound collection or directly from the
TabControl.
The easiest way is to use the MetroTabItem. It comes with the property CloseButtonEnabled to enable/disable the close button. You can also bind a command to the CloseTabCommand and CloseTabCommandParameter.
<TabControl xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls">
<Controls:MetroTabItem Header="The Header of the TabItem"
CloseButtonEnabled="True"
CloseTabCommand="{Binding CloseTabCommand}"
CloseTabCommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Header}">
<!-- your content of the TabItem -->
</Controls:MetroTabItem>
</TabControl>
Hope this helps.

WPF Custom Control Work by itself but not as part of another control

I have a WPF Custom Control that I use to display images.
There is a list view which is bound to an observable collection of database entities, which is subsequently converted into an image by virtue of a value converter.
When I drag the control onto a windows forms project (using a WPF Host control) it works perfectly when assigning the observable collection behind a list. I have tested this and it updates correctly and does everything i need it to.
HOWEVER
I would like to have three such controls displaying related images so I created a second control which simply grouped the three original controls into a stack panel.
I created a method for each that updates the images property.
public void ChangeSearchResults(List<ItemImage> items)
{
SearchResultsImageViewer.ItemImages = new ObservableCollection<ItemImage>(items);
}
However I simply cant get the images to show.
There seems to be a difference between viewing a control directly and viewing a control as a child control.
I am pretty sure it is not the element host in winforms as the control works well by itself.
Is there something I am not realising?
This is the Xaml for the list view
<!-- Sets the template for the data to be displayed -->
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- Defines the actual image being displayed -->
<Image x:Name="ItemImageControl"
Width="100"
Height="200"
Margin="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Cursor="Hand"
Source="{Binding .,
Converter={StaticResource imageConverter}}" />
<TextBlock HorizontalAlignment="Center"
FontWeight="Bold"
Text="{Binding .,
Converter={StaticResource groupNameConverter}}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
EDIT - this is the user control XAML
<UserControl x:Class="Project.CustomControls.ctrlImageCollection"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CustomControls="clr-namespace:Project.CustomControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel>
<CustomControls:ctrlImageViewer x:Name="ShortlistImageViewer" />
<CustomControls:ctrlImageViewer x:Name="SearchResultsImageViewer" />
<CustomControls:ctrlImageViewer x:Name="GroupImageViewer" />
</StackPanel>
</UserControl>
With using
...
Source={Binding ., Converter={StaticResource imageConverter}}" />
you bind to the current UserControl. When you use your ListView alone, it is bound to the UserControl containing the ListView and will work.
If you put your ListView UserControl into another UserControl its values get bound to its parent UserControl, not longer "own" UserControl.
Try this:
Go to your ListView UserControl xaml.cs and set the DataContext to itself.
//...
DataContext = this;
//...

Navigate properly between tab in a WPF MVVM application

I'm doing a WPF application with the M-V-VM patern (i'm using galasoft if it's relevant), but I have issues when I navigate through a tabcontrol.
I'm adding tabs on the run. All the binding seems to goes well : inside the tab or in the header of the tab.
I've bind my tabcontrol to a observable list. Through an interface I'm adding several types of viewmodel to this list and the binding seems correct.
My XAML code looks like this :
<Grid>
<Grid.Resources>
<DataTemplate x:Key="itemTemplate">
<TechnicalControls:ItemTab />
</DataTemplate>
</Grid.Resources>
<TabControl Grid.Row="1" x:Name="MainTab" Grid.Column="1"
ItemsSource="{Binding TabViewModels}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
ItemContainerStyleSelector="{StaticResource LastItemStyleSelector}"
ItemTemplate="{StaticResource itemTemplate}"
>
<TabControl.Resources>
<DataTemplate DataType="{x:Type VM:JobViewModel}" x:Shared="False" >
<FunctionnalControls:Job />
</DataTemplate>
<DataTemplate DataType="{x:Type VM:ExcelJobViewModel}" x:Shared="False">
<FunctionnalControls:ExcelJob />
</DataTemplate>
<DataTemplate DataType="{x:Type VM:MonitoringViewModel}" x:Shared="False">
<FunctionnalControls:Monitoring />
</DataTemplate>
<DataTemplate DataType="{x:Type VM:ErrorViewModel}" x:Shared="False">
<FunctionnalControls:Error />
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
For example if I go from a ExcelJob to another ExcelJob usercontrol, the new usercontrol is not load properly but it changes then it works, for exemple, I can go to a ExcelJob to another ExcelJob if only I go through the monitoring.
I've already look to this this but it didn't work for me.
I've also looked at this : it says that we should not used inputs because you can focus them. I've tried to set the IsEnabled property on the users controls to false. I did it when tabs were changing. It didn't work...
The only solution that I can see is to go through another a new usercontrol with no other purpose to be used every time a tab is changed but this is ugly, and I'm pretty sure, Microsoft thought about this and came up with a better solution.
If necessary I can put the code of the view model.
EDIT : Just to clarify, when I click on other tab with the same control, instead of showing me the new usercontrol, it shows me the previous one. In order to see the new one, I have to change to another tab with another usercontrol then come back on the one I want to see.
I've look through debug and the when I click on the other tab It doesn't call the viewmodel
<UserControl x:Class="App.ExcelJob"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
DataContext="{Binding Main.ExcelJobVM, Source={StaticResource Locator }}">
<Grid >
<Label>Futur Excel Job</Label>
<TextBox Width="200" Height="60" Text="{Binding Header}"/>
</Grid>
</UserControl>
So Main returns the Mainviewmodel and Main.ExcelJobVM returns the good viewmodel of the usercontrol. the returned isntance is based on selected Index.
The only thing I need is to force the redrawing of the usercontrol or recall the method to update the datacontext, by loading the good viewmodel. I tried, I've failed so far. I'm not sure of what I'm doing because I want to use the event SelectionChanged of the tabcontrol but it would be in the code behind, and I don't know if it would still respect the MVVM pattern.
The problem is that you have the DataContext hardcoded in your UserControl, and your UserControl is a Template.
When you switch to a tab that uses the same Template, WPF doesn't bother to redraw the template and only changes the DataContext behind the template. But in your case, you have the DataContext hardcoded in the UserControl, so it's not using the existing data context from the TabItem.
The best solution would be to remove the DataContext binding from your UserControl, and let it be inherited from the TabItem when the selected item changes.
For example:
WPF says
User has selected ExcelJobA for display. Because of the DataTemplate, let me draw it using an ExcelJob UserControl
<TabItem>
<ContentPresenter DataContext="ExcelJobA">
<local:ExcelJob DataContext="{Binding Main.ExcelJobVM, Source={StaticResource Locator }}" />
</ContentPresenter>
</TabItem>
So an ExcelJob UserControl gets created, and by default the DataContext of ExcelJobA would be inherited by the UserControl.
When the user changes the selected tab to ExcelJobB, WPF goes
Hey, the user has changed to ExcelJobB. Because of the DataTemplate, let me draw it using an ExcelJob UserControl, BUT WAIT! I'm already displaying an ExcelJob UserControl, so let me just change the DataContext behind that to ExcelJobB
<TabItem>
<ContentPresenter DataContext="ExcelJobB">
<local:ExcelJob DataContext="{Binding Main.ExcelJobVM, Source={StaticResource Locator }}" />
</ContentPresenter>
</TabItem>
So the actual displayed ExcelJob UserControl does not get recreated or redrawn, but only the DataContext behind it changes.
HOWEVER, because you have hard-coded the DataContext inside your UserControl, the actual data context obtained from the selected item doesn't ever get used, because a DataContext specified from inside a <Tag> always takes precedence over a DataContext that would be inherited from further up the visual tree.
You need to remove the DataContext binding from inside your UserControl, and let it get passed in normally from your TabControl's SelectedItem, and it will work fine.
<TabItem>
<ContentPresenter DataContext="ExcelJobA">
<local:ExcelJob /> <!-- DataContext inherited from the ContentPresenter -->
</ContentPresenter>
</TabItem>

Categories