Content view binding not working - forms

I have ContentView which need ViewModel binding
Test.xaml
<ContentView.Content>
<Frame x:Name="HelpBaseFrame" BackgroundColor="White" CornerRadius="16" HorizontalOptions="Fill" VerticalOptions="Center">
<StackLayout>
<ListView HasUnevenRows="True" x:Name="lstview" SeparatorColor="White">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell IsEnabled="False">
<ScrollView x:Name="ScrollView" Orientation="Vertical" Padding="0, 1, 0, 0" BackgroundColor="Transparent" HorizontalOptions="Center">
<StackLayout>
<Label x:Name="LabelHeader" FontAttributes="Bold" Font="HiraginoSans-W6, 16"
HorizontalOptions="Center" Margin="0,20,0,0">
<Label.Text>
<Binding Path="HeaderData"></Binding>
</Label.Text>
</Label>
<local:LineSpacingLabel x:Name="LabelHeaderDesceiption" LineSpacing="6"
Font="HiraginoSans-W3, 16" FontAttributes="None" Margin="0,20,0,0">
<Label.Text>
<Binding Path="DescriptionData"></Binding>
</Label.Text>
</local:LineSpacingLabel>
</StackLayout>
</ScrollView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</Frame>
</ContentView.Content>
BindingClass
public void SetData(Dictionary<string, string> dictionary)
{
............
lstview.ItemsSource = HelpDataList; // HelpDataList is observable collection of HElp Data
}
Model class :
public class HelpData : BaseViewModel
{
private string Header = string.Empty;
private string Description = string.Empty;
public string HeaderData
{
get { return Header; }
set
{
Header = value;
OnPropertyChanged("HeaderData");
}
}
public string DescriptionData { get; set; }
}
This view model for above view.
This binding is not working.
Is anything wrong?
This view model for above view.
This binding is not working.
Is anything wrong?
This view model for above view.
This binding is not working.
Is anything wrong?

You have to set bindingContext on the target control by using x:Reference markup extension.
BindingContext="{x:Reference Name=ViewModelField}"
Or
BindingContext="{x:Reference ViewModelField}"
Label.Text should be binding with string not viewModel.
Text="{Binding Path=Value}"
Or
Text="{Binding Value}" ( “Path=” part of the markup extension can be omitted if the path is the first item in the Binding markup extension)
Refer to Bindings
Update
<Binding> is not a valid tag.
Modify the label :
<Label Text="{Binding Path = HeaderData}">

Related

Child property of an ObservableProperty is not updating

Something isn't right with the XAML but it's not sticking out at me.
I've been working on the layout of one of my .net Maui XAML pages. I added a collectionView when I noticed that the top data was no longer showing. The other pages are working fine.
What's weird is that the data is there and while running the app in debug mode if I highlight, shift-delete, then paste it back in the bound data appears. I also noticed if I change the {Binding EditEvent.name} by removing the "name" from EditEvent then adding it back on, the view displays the data as well.
But if I leave and navigate back in the data won't show up until I repeat the above process. It's like the viewModel isn't updating the view when the data changes. But if I force the view to update by deleting and re-pasting it will show it.
Anyone have an idea what possibly could be the issue?
I've got 2 ObservableProperties in my ViewModel:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Newtonsoft.Json;
using SharedModels;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyApp.ViewModels
{
public partial class EditEventViewModel : ObservableObject
{
#region XAML page Observables
[ObservableProperty]
attEventDx editEvent;
[ObservableProperty]
ObservableCollection<groupReturn> groupsItems;
#endregion
// pass object to edit into this view
public async void SetEditEvent(attEventDx incomingEvent)
{
editEvent = incomingEvent;
//await LoadGroupsAsync();
}
...
}
And this is the view:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Pages.EditEventPage"
Title="Edit Event"
xmlns:viewmodel="clr-namespace:MyApp.ViewModels"
xmlns:dm="clr-namespace:SharedModels;assembly=SharedModels"
x:DataType="viewmodel:EditEventViewModel"
NavigatedTo="ContentPage_NavigatedTo">
<VerticalStackLayout>
<Grid HorizontalOptions="Center" VerticalOptions="Start" Padding="0,40,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label Text="Event Name" VerticalOptions="Center" HorizontalOptions="Center" Grid.Column="0" Grid.Row="0"/>
<Entry Text="{Binding EditEvent.name}" WidthRequest="200" Grid.Column="1" Grid.Row="0"/>
<Label Text="Event Date" VerticalOptions="Center" HorizontalOptions="Center" Grid.Column="0" Grid.Row="1"/>
<Entry Text="{Binding EditEvent.happeningOn}" WidthRequest="200" Grid.Column="1" Grid.Row="1"/>
</Grid>
<Label Text="Selectable Groupings" VerticalOptions="Center" HorizontalOptions="Center" Padding="20"/>
<CollectionView ItemsSource="{Binding GroupsItems}" SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="dm:groupReturn">
<SwipeView>
<SwipeView.RightItems>
<SwipeItem Text="Delete" BackgroundColor="Red"/>
</SwipeView.RightItems>
<Grid Padding="0,5">
<Label Text="Groups"/>
<ScrollView>
<Frame>
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:EditEventViewModel}}, Path=TapCommand}"
CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>
<Label Text="{Binding groupName}" FontSize="20" FontAttributes="Bold"/>
</Frame>
</ScrollView>
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
this is my xaml.cs for that page:
public partial class EditEventPage : ContentPage, IQueryAttributable
{
EditEventViewModel _vm;
attEventDx _editEvent;
public EditEventPage( EditEventViewModel vm)
{
InitializeComponent();
_vm = vm;
BindingContext = _vm;
}
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
_editEvent = query["EditEvent"] as attEventDx;
}
private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
{
_vm.SetEditEvent(_editEvent);
}
}
attEventDx for reference (sits in another shared project between Azure Functions and the mobile app):
namespace SharedModels
{
public class attEventDx
{
public Guid? publicId { get; set; }
public int? createdBy { get; set; }
public string name { get; set; }
public DateTime dateCreated { get; set; }
public DateTime? happeningOn { get; set; }
}
}
As I referred to this is the page that IS working:
xaml.cs:
public partial class EventPage : ContentPage
{
EventViewModel _vm;
public EventPage(EventViewModel vm)
{
InitializeComponent();
_vm = vm;
BindingContext= _vm;
}
private async void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
{
await _vm.LoadEventData();
}
private void ImageButton_Clicked(object sender, EventArgs e)
{
}
}
ViewModel:
public partial class EventViewModel : ObservableObject
{
#region XAML page Observables
[ObservableProperty]
ObservableCollection<attEventDx> eventItems;
[ObservableProperty]
attEventDx selectedEvent;
[ObservableProperty]
string text;
#endregion
public EventViewModel()
{
//EventItems = new ObservableCollection<attEventDx>();
}
[RelayCommand]
public async Task LoadEventData()
{
MyApp.globals.SetHttpClient();
try
{
var response = await MyApp.globals.httpClient.GetAsync(MyApp.globals.APIURL + "getEvents");
var allEvents = response.Content.ReadAsStringAsync().Result;
if (allEvents != null)
{
List<attEventDx> listOfEvents = JsonConvert.DeserializeObject<List<attEventDx>>(allEvents);
if (listOfEvents != null)
{
EventItems = new ObservableCollection<attEventDx>(listOfEvents);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message + "\r\b" + ex.StackTrace);
}
}
[RelayCommand]
async Task Add()
{
await Shell.Current.GoToAsync($"{nameof(AddEventPage)}");
}
[RelayCommand]
async Task Tap(attEventDx sender)
{
selectedEvent = sender;
var navigationParameter = new Dictionary<string, object>
{
["EditEvent"] = selectedEvent
};
await Shell.Current.GoToAsync($"{nameof(EditEventPage)}", navigationParameter);
}
[RelayCommand]
async Task Refresh()
{
await LoadEventData();
}
}
And the view of the working page:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.EventPage"
Title="Events"
xmlns:viewmodel="clr-namespace:MyApp.ViewModels"
xmlns:dm="clr-namespace:SharedModels;assembly=SharedModels"
x:DataType="viewmodel:EventViewModel"
NavigatedTo="ContentPage_NavigatedTo">
<Grid RowDefinitions="100, Auto, 30, *"
ColumnDefinitions=".50*, .25*, .25*"
Padding="10">
<Image Grid.ColumnSpan="3"
Source="logo.png"
BackgroundColor="Transparent"/>
<ImageButton Source="plus.png" Grid.Row="0" Grid.Column="2" Scale=".7" Command="{Binding AddCommand}"></ImageButton>
<Label Text="New Event" Grid.Column="2" Grid.Row="0" HorizontalOptions="Center" VerticalOptions="End"></Label>
<!--<Entry Placeholder="Enter Text" Grid.Row="1" Text="{Binding Text}" />-->
<!--<Button Text="Search" Grid.Row="1" Grid.Column="1" />-->
<!--<Button Text="Add" Grid.Row="1" Grid.Column="2" Command="{Binding AddCommand}"/>-->
<Label Text="Upcoming Events" FontSize="22" Grid.Row="2"/>
<!--<Button Text="Refresh" Grid.Row="2" Grid.Column="2" Command="{Binding RefreshCommand}"/>-->
<CollectionView Grid.Row="3" Grid.ColumnSpan="3" ItemsSource="{Binding EventItems}" SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="dm:attEventDx">
<SwipeView>
<SwipeView.RightItems>
<SwipeItem Text="Delete" BackgroundColor="Red"/>
</SwipeView.RightItems>
<Grid Padding="0,5">
<Label Text="Event"/>
<ScrollView>
<Frame>
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:EventViewModel}}, Path=TapCommand}"
CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>
<Label Text="{Binding name}" FontSize="20" FontAttributes="Bold"/>
</Frame>
</ScrollView>
<Label Text="{Binding happeningOn}" HorizontalOptions="End" VerticalOptions="Center" Padding="0,0,5,0"></Label>
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
Well, what ToolmakerSteve told me kind of worked, the items were initially displaying but not updating.
I then decided to build out another page in the app and do some experimenting along the way and figured out the issue.
When I created the new page by hand, I still had this issue and was doubting my sanity. I then partially copied in the page that was working and it worked! In comparing the two pages closely, I finally discovered what the problem was.
I was right, the CommunityToolkit's [ObservableProprerty] DOES work for all child items in an object; this is why I selected using this library from the start. I wasn't going crazy... (At least not on this)
This particular app was started a few months ago but then I got pulled into another project in another platform for a few months so what I had learned was partially forgotten when I picked it back up recently.
When you define a [ObservableProperty] like this:
[ObservableProperty]
myObject usedVariable;
The "usedVariable" will contain the data, but not the framework for INotifyPropertyChanged. CommunityToolkit builds out the framework on "UsedVariable".
While this code is "legal" in the ViewModel:
usedVariable = new myObject();
It will assign the data, but not the notification framework.
Instead it needs to be:
UsedVariable = new myObject();
Once the variable is defined with lowercase, you will never reference the variable that way again (as far as I can tell anyway). Instead, you will use the uppercase "UsedVariable".
When I referenced the lowercase version of the variable, I didn't see the data on the app page when it started. However, if I had the page open and I removed the XAML code for that control and pasted it back in, the data did show.
It's always something simple that causes the most grief...

How to bind attributes values in MAUI component

I'm trying to make a windows-like navigation item.
This is the implementation:
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="NavItem.NavItem"
xmlns:local="clr-namespace:NavItem"
x:DataType="local:NavItem">
<Frame HeightRequest="50" CornerRadius="6" BackgroundColor="{StaticResource Tertiary}" BorderColor="{StaticResource Secondary}" Padding="10">
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" />
</Frame.GestureRecognizers>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label x:Name="LblText" Text="{Binding Text}" Grid.Column="0" TextColor="{StaticResource White}" />
<Label Text=">" Grid.Column="1" TextColor="{StaticResource White}" FontSize="16" />
</Grid>
</Frame>
</ContentView>
public partial class NavItem : ContentView
{
string text;
public string Text
{
get => text;
set { text = value; OnPropertyChanged(); }
}
public string PageName { get; set; }
public NavItem()
{
InitializeComponent();
}
private async void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
await Shell.Current.GoToAsync(PageName);
}
}
<VerticalStackLayout>
<local:NavItem Text="Page 1" PageName="Page1" />
<local:NavItem Text="Page 2" PageName="Page2" />
</VerticalStackLayout>
The navigation is ok but the binding fails to set Text property of label LblText.
So this is the rendering:
EDIT: I also tried with a bindable attribute, same result
public string Text
{
get => (string)GetValue(TextProperty);
set { SetValue(TextProperty, value); OnPropertyChanged(); }
}
public static readonly BindableProperty TextProperty =
BindableProperty.Create(nameof(Text), typeof(string), typeof(NavItem), "", BindingMode.TwoWay);
I just had to set the binding context to itself:
public NavItem()
{
InitializeComponent();
BindingContext = this;
}

How can I make a list view have alternating background colors? NET MAUI

I am trying to create a list view where the background color alternates for each entry in the list. Is there a way to do this in MAUI?
This can be done in multiple ways.And the method often recommended is through a DataTemplateSelector.
1.Create a DataTemplateSelector that holds two templates and selects them based on the index of the item:
public class AlternateColorDataTemplateSelector: DataTemplateSelector
{
public DataTemplate EvenTemplate { get; set; }
public DataTemplate UnevenTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
// TODO: cast `Monkey` to your Item
return ((List<Monkey>)((ListView)container).ItemsSource).IndexOf(item as Monkey) % 2 == 0 ? EvenTemplate : UnevenTemplate;
}
}
2.In XAML, we can define two templates, one with the alternate color and one with the normal color.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp0606.MainPage"
xmlns:local="clr-namespace:MauiApp0606"
>
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="evenTemplate">
<ViewCell>
<Grid BackgroundColor="White">
<Label Text="{Binding Name}" HorizontalOptions="Center" VerticalOptions="Center" />
</Grid>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="unevenTemplate">
<ViewCell>
<Grid BackgroundColor="LightGray">
<Label Text="{Binding Name}" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" />
</Grid>
</ViewCell>
</DataTemplate>
<local:AlternateColorDataTemplateSelector x:Key="alternateColorDataTemplateSelector"
EvenTemplate="{StaticResource evenTemplate}"
UnevenTemplate="{StaticResource unevenTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
<VerticalStackLayout Spacing="25" Padding="30">
<ListView ItemsSource="{Binding Monkeys}" ItemTemplate="{StaticResource alternateColorDataTemplateSelector}">
</ListView>
</VerticalStackLayout>
</ContentPage>
To solve this problem I added a LineNumber property to my view model and used a value converter to set the color.
The value converter:
public class NumberToAlternatingColorValueConverter : IValueConverter
{
public Color EvenNumberColor { get; init; } = Colors.Green;
public Color OddNumberColor { get; init; } = Colors.Red;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is not int intValue
? Colors.Transparent
: (intValue % 2) == 0
? EvenNumberColor
: OddNumberColor;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("NumberToAlternatingColorValueConverter.ConvertBack");
}
}
Register the value converter
I registered the value converter in my main resource dictionary like so:
<converter:NumberToAlternatingColourValueConverter
x:Key="MagentaCyanRowColorConverter"
EvenNumberColour="Magenta"
OddNumberColour="Cyan" />
You can register more than one value converter with a different key for different colour combinations.
Use the value converter
And I used the bound value converter in my XAML like so:
<StackLayout BindableLayout.ItemsSource="{Binding Summary.Lines}">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="vm:SummaryLineViewModel">
<Grid BackgroundColor="{Binding LineNumber, Converter={StaticResource MagentaCyanRowColorConverter}}">
<Label Grid.Column="0" Text="{Binding Name}" />
<Label Grid.Column="1" Text="{Binding Value}" />
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
As in this answer we can used DataTemplateSelector to resolve your problem.
Alternative solutions,
You can use a property in the model and straight a way bind it to the row background. (In your case Grid's Background color)
You can use triggers make the color change by considering a logic.
( Xamarin Forms Triggers )

How to detect clickable label position inside listview xamarin forms

I'm trying to do something like this
Like This
I have Listview with clicked label " Edit " I want when i click this label it's position is detected and display
label at Aliaddress is clicked
I Used TapGestureRecognizer for this but when i google it i fount that selected item doesn't work with TapGesture
This is my xaml
<ListView ItemsSource="{Binding UserAdresses}" SelectedItem="{Binding SelectedAddress}" HorizontalOptions="{Binding HoriRLLR}" RowHeight="{Binding RowHeight}" VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout VerticalOptions="FillAndExpand">
<Label Text="{Binding Country}" TextColor="Orange" FontSize="Large" FontAttributes="Bold"></Label>
<Label Text="{Binding Address}" TextColor="Black" FontSize="Medium"></Label>
<Label Text="{Binding City}" TextColor="Black" FontSize="Medium"></Label>
<Label Text="{translator:Translate Edit}" TextColor="Black" FontSize="Medium">
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.EditAddressesCommand, Source={x:Reference CustomerAdressesPage}}"/>
</Label.GestureRecognizers>
<Label.Effects>
<controls:UnderlineEffect></controls:UnderlineEffect>
</Label.Effects>
</Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
my code
public DelegateCommand EditAddressesCommand => new DelegateCommand(EditAddresses);
public DelegateCommand DeleteAddressesCommand => new DelegateCommand(DeleteAddresses);
private readonly IPageDialogService _dialogService;
private ObservableCollection<CustomerAdressesModel> _userAdresses;
public ObservableCollection<CustomerAdressesModel> UserAdresses
{
get { return _userAdresses; }
set { SetProperty(ref _userAdresses, value);
}
}
private CustomerAdressesModel _selectedAddress;
public CustomerAdressesModel SelectedAddress
{
get { return _selectedAddress; }
set { SetProperty(ref _selectedAddress, value); }
}
private void EditAddresses()
{
_dialogService.DisplayAlertAsync("Test", "Edit Clicked", "Ok");
}
How can i do this and detect the position of clicked label
You can use this: CommandParameter="{Binding .}" inside TapGestureRecognizer
Xaml:
<Label.GestureRecognizers>
<TapGestureRecognizer
CommandParameter="{Binding .}"
Command="{Binding Path=BindingContext.EditAddressesCommand, Source={x:Reference CustomerAdressesPage}}"/>
</Label.GestureRecognizers>
ViewModel:
public ICommand EditAddressesCommand
{
get
{
return new Command<YourModel>((YourModel model) =>
{
//Access your model properties
});
}
}
Hope this may solve your problem.

Collapse/Visible UserControls on ButtonClick with MVVM - no swap mechanism -

In my scenario I have a MainView + MainViewModel, UserControl1 + UserControl 2.
In the MainView I have 2 buttons labeled: Button_ShowUserControl1 + Button_ShowUserControl2.
At the lower part of the MainView I have a "ContentGrid" which takes/should_take... every UserControl.
My goal:
When Button_ShowUserControl1 is clicked UserControl1 is Visible and UserControl2 OR any other UserControl must be set to Collapsed. Same is valid for Button_ShowUserControl2.
My problem:
1.) As the UserControls shall be loaded at application start how can I put them all together in the one "ContentGrid"? Thats actually not possible... so how can I make one UserControl visible while the other is in the same place/"ContentGrid" just collapsed ?
2.) As 1.) seems not possible how can I instantiate all UserControls at start of the application and make them only Visible/Collapsed when respective Button is clicked?
3.) As a UserControl has a property Visibility = Visible/Hidden/Collapsed, how can I bind to a property in a ViewModel return such a value like Collapsed? I only could get a boolean value like Visibility = false/true ?
My testcode:
<Grid x:Name="LayoutRoot" Background="#FFBDF5BD" ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="96*" />
<RowDefinition Height="289*" />
</Grid.RowDefinitions>
<Grid HorizontalAlignment="Stretch" Name="MenuGrid" VerticalAlignment="Stretch" Background="#FFCECEFF">
<StackPanel Name="stackPanel1" Background="#FFEDFF00" Orientation="Horizontal">
<Button Content="User Data 1" Height="35" Name="button1" Command="{Binding Path=ShowUserControl1Command}" Width="150" Margin="100,0,0,0" />
<Button Content="User Data 2" Height="35" Name="button2" Width="150" Margin="100,0,0,0" />
</StackPanel>
</Grid>
<Grid Grid.Row="1" HorizontalAlignment="Stretch" Name="ContentGrid" VerticalAlignment="Stretch" Background="#FFB15454" />
</Grid>
<UserControl x:Class="SwapUserControls.MVVM.UserControl2"
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"
xmlns:vm="clr-namespace:SwapUserControls.MVVM.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Visibility="{Binding IsUserControl1Collapsed, Path=Value}">
<UserControl.Resources>
<vm:MainViewModel x:Key="MainViewModelID" />
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource MainViewModelID}" />
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="228*" />
<RowDefinition Height="72*" />
</Grid.RowDefinitions>
<Button Content="UserControl2" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="112,27,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
<DataGrid HorizontalAlignment="Stretch" Name="dataGrid1" VerticalAlignment="Stretch" Background="#FFC046F8" />
</Grid>
public class MainViewModel : ViewModelBase
{
RelayCommand _ShowUserControl1Command;
private bool _IsUserControl1Collapsed;
public RelayCommand ShowUserControl1Command
{
get
{
if (_ShowUserControl1Command == null)
{
_ShowUserControl1Command = new RelayCommand( () => ShowUserControl1() );
}
return _ShowUserControl1Command;
}
}
public void ShowUserControl1()
{
_IsUserControl1Collapsed = true;
}
public bool IsUserControl1Collapsed
{
get
{
return _IsUserControl1Collapsed;
}
}
}
Yes the code is wrong, therefore I ask here :)
You only have 2 things wrong with this code.
1) You can't set the visibility of a usercontrol directly... you have to set it on a container:
<Grid Visibility="Collapsed">
<myControls:MyUserControl />
</Grid>
2) Visibility is not a boolean value, it is an enum. As such, you will need to use a converter to convert from boolean to Visibility. Observe:
<Window ...>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>
<Grid Visibility="{Binding ShouldShowUsercontrol1, Converter={StaticResource BoolToVis}}">
<myControls:MyUserControl />
</Grid>
</Window>
That should be it. Hope this helps.
There are other things that you are leaving clues about that might affect the ability of this to work. For example, you don't show the biggest container element... are you wrapping everything in a StackPanel? If you are wrapping everything in a Grid, for example, the controls will overlay everything in layers.
Try these changes I suggest... it should get you closer.
Edit: Another idea using data templates
Another thing you could do is make sure you have a unique ViewModel for each of these views you want to show and hide:
public class MyFirstViewModel : ViewModel
{
}
public class MySecondViewModel : ViewModel
{
}
Then from your "parent" or "main" ViewModel, you show or hide the views you want by virtue of having them in a collection of ViewModels:
public MyMainViewModel : ViewModel
{
public ObservableCollection<ViewModel> ViewsToShow
{
...
}
public void ShowFirstViewModel()
{
ViewsToShow.Add(new MyFirstViewModel());
}
}
To wire everything up in your view, you would then datatemplate these types with their user controls (but this would not cause those views to be instantiated unless they were needed:
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type myViewModels:MyFirstViewModel}">
<myViews:MyFirstView />
</DataTemplate>
<DataTemplate DataType="{x:Type myViewModels:MySecondViewModel}">
<myViews:MySecondView />
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding ViewsToShow}" />
</Window>
And for any ViewModels you put in "ViewsToShow", the view will automatically see that and template in the appropriate view. Again, without instantiating it before it's needed.
This is probably a little cleaner than putting everything single thing in the View and setting visibility, but it would be dependent on you have a unique view model type for every view, which might not be the case.
The question of saving state comes up when using the DataTemplated approach. The solution here is to tread your ViewModel as the state of the control and design both your ViewModels and your Views accordingly. Here is an example that allows you to swap out your Views using DataTemplating, but switching back and forth saves state.
Assume you have the setup from the last section w/ 2 viewmodels that have datatemplates defined. Let's change up the MainViewModel a little:
public MyMainViewModel : ViewModel
{
public RelayCommand SwapViewsCommand
{
...
}
public ViewModel View
{
...
}
private ViewModel _hiddenView;
public MyMainViewModel()
{
View = new MyFirstViewModel();
_hiddenView = new MySecondViewModel();
SwapViewsCommand = new RelayCommand(SwapViewModels);
}
public void SwapViewModels()
{
var hidden = _hiddenView;
_hiddenView = View;
View = hidden;
}
}
And a few changes to the main view. I've omitted the DataTemplates for brevity.
<Window ...>
<!-- DataTemplates Here -->
<Button Command="{Binding SwapViewsCommand}">Swap!</Button>
<ContentControl Content="{Binding View}" />
</Window>
That's it. The secret here is I'm saving the reference to the original view model. This way, let's say there is a string property in a viewmodel and an associated textbox in the DataTemplated usercontrol with a two-way binding then the state will essentially be saved.