xamarin form does not knows controls (inside ListView.ItemTemplate's DataTemplate) by its x:Name - xamarin.forms

I have this in my xaml
<StackLayout VerticalOptions="CenterAndExpand">
<ListView x:Name="listViewItems" Margin="1,1" BackgroundColor="White" HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="3" VerticalOptions="FillAndExpand" >
<!-- BorderColor="Gray" BackgroundColor="{StaticResource ElementBackgroundColor}"> -->
<Grid BackgroundColor="White" ColumnSpacing="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label x:Name="labelCode" Grid.Column="0" Grid.RowSpan="2" Text="{Binding Code}" VerticalOptions="Center" HorizontalOptions="FillAndExpand" TextColor="{StaticResource TextColor}" BackgroundColor="White" FontAttributes="Bold" FontSize="{StaticResource FontSizeLabelLittle}" />
<StackLayout Grid.Column="1" Spacing="0" Orientation="Vertical" VerticalOptions="StartAndExpand" HorizontalOptions="StartAndExpand">
<Label x:Name="labelEnglish" Text="{Binding NameEnglish}" VerticalOptions="Start" TextColor="{StaticResource TextColor}" BackgroundColor="White" FontAttributes="Bold" FontSize="{StaticResource FontSizeLabelLittle}" />
<Label x:Name="labelRussian" Text="{Binding NameRussian}" VerticalOptions="StartAndExpand" TextColor="{StaticResource TextColor}" BackgroundColor="White" FontAttributes="Bold" FontSize="{StaticResource FontSizeLabelLittle}" />
</StackLayout>
</Grid>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
And I want to do this in code
labelCode.IsVisible = false;
But I get compile error
Severity Code Description Project File Line Suppression State
Error CS0103 The name 'labelCode' does not exist in the current
context gttCompound C:\Development\Guido\Xamarin\gttCompound\gttCompound\Pages\PageViewOrSelectItem.xaml.cs 33 Active
I found a question/answer here with the same problem, but I checked and tried it all with no result
So I am at a loss here, how do I get my form to know these controls ?
EDIT
I also find more similar questions, but the always want to fill up an element in the listview in the code behind, that is not my problem.
I use binding for all the content.
My problem is that I want to hide one or two labels in the listview.
I have now 3 labels in the listview, but in some cases not all 3 will be filled, and when I just leave it like it is than the binding will show them empty, but it makes the gridrow to large in height, so I wanted to see if in these cases I can hide the empty labels to the gridrow will not be so high anymore.
I have now done this by building up the gridcontent in the code behind, which until now seems to work.

You get error because you cannot directly access objects by name in page code-behind (.cs file) within DataTemplate of ListView. If you really want to access this by name you then you should move <ViewCell> to the new .xaml file, then you could access objects by name in code-behind.
As #ewerspej mentioned "You cannot reference elements inside a DataTemplate by their x:Name. The reason being that DataTemplate are used to instantiate VisualElements dynamically at runtime and the names must be unique identifiers."
But the real solution is to use MVVM approach and DataBinding, so you wouldn't change ViewCells objects directly, but rather modify Model class of the given view cell. You would need to implement INotifyPropertyChanged interface into ViewModel, and then use binding for IsVisible property in your ViewCell
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/data-and-databinding.
Take a look at this implementation of very simple MVVM pattern:
this is a MainPage.xaml content
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App1"
x:Class="App1.MainPage"
x:DataType="local:MyPageViewModel">
<StackLayout VerticalOptions="CenterAndExpand">
<ListView ItemsSource="{Binding ListViewItemsSource}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="cell" x:DataType="local:ListViewModel">
<Label Text="{Binding Title}"
IsVisible="{Binding IsVisible}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
we set here binding for ItemsSoruce for ListView and also we bind Title & IsVisible properties.
this is MainPage.xaml.cs
using Xamarin.Forms;
namespace App1
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MyPageViewModel();
}
}
}
we set there BindingContext for whole page.
and those are models:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace App1
{
public class ListViewModel : BaseModel
{
private string _title;
private bool _isVisible;
// properties that will fire PropertyChanged (auto genertated via JetBrains Rider)
public string Title
{
get => _title;
set => SetField(ref _title, value);
}
public bool IsVisible
{
get => _isVisible;
set => SetField(ref _isVisible, value);
}
}
public class MyPageViewModel : BaseModel
{
// Data source for your ListView
public ObservableCollection<ListViewModel> ListViewItemsSource { get; }
public MyPageViewModel()
{
// Init source for ListView
ListViewItemsSource = new ObservableCollection<ListViewModel>(new[]
{
new ListViewModel() { IsVisible = true, Title = "1" },
new ListViewModel() { IsVisible = true, Title = "2" },
new ListViewModel() { IsVisible = true, Title = "3" }
});
// Change second item to be invisible after 2 seconds
Task.Run(async () =>
{
await Task.Delay(2000);
ListViewItemsSource[1].IsVisible = false;
});
}
}
// base class that implements INotifyPropertyChanged (auto genertated via JetBrains Rider)
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
}
I've created Base Model, that can be used as base class for models for ViewCells and ViewModels for whole pages. In View Model im creating New list of items that are binded to View. In constructor I added delay to show that changing IsVisible property in Models changes also presentation of the View`.
In the ListViewModel i've created properties that can bind to View. Look how i call SetField in setters - this is the main idea - Model notifies View about change.
Most of the implementation code was autogenerated via JetBrains Rider IDE, but i think that Visual Sutido also can auto that basic code.

Based on refer to controls inside ListView / add new property to model,
you could bind IsVisible property of each label to a property in the model:
<Label x:Name="labelEnglish" IsVisible="{Binding HasEnglish}" .../>
Add to the model:
public bool HasEnglish => !string.IsNullOrEmpty(NameEnglish);

Related

How do i make a button read the text(url) stored in the database so it runs the xamarin essentials share function

Ok so i followed a few tutorials to make it so my app can read a database file and used this https://github.com/jfversluis/ExistingSQLiteDbSample
And it did work for me, but now what I'm trying to do is so my app can use the text stored in the database so it can do a share function using Xamarin.Essentials: Share
I would prefer it was a button but no idea were to even begin (since i want the button to be a image)
The code of my main page is this (its almost 1:1 with the first link), the data that i want to turn into a button is "LocationLink" which i temporary have setup as a Label
MainPage.xaml
<StackLayout>
<CollectionView ItemsSource="{Binding List}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<BoxView HeightRequest="1" Color="#000000" IsVisible="true"/>
<Label Text="{Binding LocationName}"/>
<!-- Bellow is what i need help with-->
<Label Text="{Binding LocationLink}"/>
<Button/>
<!-- Above is what i need help with-->
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Item.cs
public class Temp
{
public Int Id { get; set; }
public string LocationName { get; set; }
public string LocationLink { get; set; }
}
<Button Text="Click Me!" Clicked="ButtonClick" />
then
protected void ButtonClick(object sender, EventArgs args)
{
// get the button
var button = (Button)sender;
// I don't know what your class is called, you will need to put
// the correct name here
var item = (MyListItem)button.BindingContext;
// now you can use item.LocationName, item.LocationLink, etc
// when calling the Share function
}

xamarin forms observable collection does not call CollectionChanged event when new collection is assigned

I have this scenario: (NOTE CODE CHANGED)
I have a user control that has a bindable property DataSource of
type ObservableCollection.
I subscribe to the CollectionChanged event of the Observable collection in my user control.
I also subscribe to the BindingContextChanged event of the user control.
When my page hosting the user control loads, I set the bindable property DataSource to an empty ObservableCollection and I set the BindingContext of control to the same thing. The BindingContextChanged event fires in my user control and the property set executes for my DataSource property.
If I manually add items to my observable collection using the .Add() method, the CollectionChanged event in my user control fires.
However, if I set the bound observable collection to a new observable collection, no events fire in my user control letting me know the contents of the observable collection changed.
If I re-set the binding context property of the user control to the new observable collection, the BindingContextChanged event fires, but my bound DataSource property still does not contain the new list data.
The way my actual application works is that I make call to a server and retrieve the current state of the list as it sits on the server, i.e. a complete replace of the existing list.
The challenge now seems to be that the Set of the DataSource property in my usercontrol is never being called. Any ideas?
Here is my code
DataGrid XAML
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ObeservableCollectionSample.DataGrid">
<ContentView.Content>
<StackLayout Orientation="Vertical" HorizontalOptions="Center" VerticalOptions="Fill" Spacing="0">
<ScrollView Orientation="Both" HorizontalOptions="Center" VerticalOptions="Fill" Margin="0,0,0,0"
HorizontalScrollBarVisibility="Default" VerticalScrollBarVisibility="Default" Padding="0,0,0,16" >
<Grid x:Name="grdDataGrid" RowSpacing="0" ColumnSpacing="0" IsVisible="false" >
</Grid>
</ScrollView>
<Label x:Name="lblNoDataMessage" HorizontalOptions="Center" VerticalOptions="Start" IsVisible="false"/>
</StackLayout>
</ContentView.Content>
</ContentView>
Here is the code behind for the user control:
using System.Collections.ObjectModel;
using System.Diagnostics;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace ObeservableCollectionSample
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class DataGrid : ContentView
{
public static readonly BindableProperty DataSourceProperty = BindableProperty.Create(nameof(DataSource), typeof(ObservableCollection<string>), typeof(DataGrid));
public ObservableCollection<string> DataSource
{
get
{
return (ObservableCollection<string>)GetValue(DataSourceProperty);
}
set
{
SetValue(DataSourceProperty, value);
if (value == null)
{
//TODO Clear datagrid
}
else
{
//going to change this to subscribe to the collection changed events
DataSource.CollectionChanged += DataSource_CollectionChanged;
}
}
}
private void DataSource_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Debug.WriteLine("DataSource_CollectionChanged Event Raised");
}
public DataGrid()
{
InitializeComponent();
this.BindingContextChanged += DataGrid_BindingContextChanged;
}
private void DataGrid_BindingContextChanged(object sender, System.EventArgs e)
{
Debug.WriteLine("DataGrid_BindingContextChanged Event Raised");
}
}
}
Then my test page simply contains the user control plus some button to test changing the collection:
Test Page XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:MyDataGrid="clr-namespace:ObeservableCollectionSample;assembly=ObeservableCollectionSample"
x:Class="ObeservableCollectionSample.MainPage">
<StackLayout>
<StackLayout Orientation="Vertical" HorizontalOptions="Fill" VerticalOptions="StartAndExpand">
<Frame BackgroundColor="#2196F3" Padding="24" CornerRadius="0">
<Label Text="Welcome to Xamarin.Forms!" HorizontalTextAlignment="Center" TextColor="White" FontSize="36"/>
</Frame>
<MyDataGrid:DataGrid x:Name="grdData" VerticalOptions="FillAndExpand" HorizontalOptions="Center"
DataSource="{Binding DataList}"/>
</StackLayout>
<StackLayout Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="End">
<Button x:Name="btnAdd1" Text="Add List 1" HorizontalOptions="Center" VerticalOptions="Center" Clicked="btnAdd1_Clicked"/>
<Button x:Name="btnAdd2" Text="Add List 2" HorizontalOptions="Center" VerticalOptions="Center" Clicked="btnAdd2_Clicked"/>
</StackLayout>
</StackLayout>
</ContentPage>
Test page code behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace ObeservableCollectionSample
{
public partial class MainPage : ContentPage
{
private ObservableCollection<string> iobj_DataList = new ObservableCollection<string>();
public ObservableCollection<string> DataList
{
get
{
return iobj_DataList;
}
set
{
iobj_DataList= value;
NotifyPropertyChanged();
}
}
public MainPage()
{
InitializeComponent();
grdData.BindingContext = this;
}
private void btnAdd1_Clicked(object sender, EventArgs e)
{
iobj_DataList.Add("String 1");
iobj_DataList.Add("String 2");
iobj_DataList.Add("String 3");
NotifyPropertyChanged("DataList");
}
private void btnAdd2_Clicked(object sender, EventArgs e)
{
List<string> myList = new List<string>();
myList.Add("String 4");
myList.Add("String 5");
myList.Add("String 6");
//I would like to have this line raise the needed events in my control
DataList = new ObservableCollection<string>(new ObservableCollection<string>(myList));
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
This can easily be done, without events or data binding.
Your control exposes a property, DataSource.
When you set iobj_DataList, that should become DataSource.
Do this in iobj_DataLists setter.
Then DataSource setter can do whatever you want.
Usage in test page:
public MainPage()
{
InitializeComponent();
iobj_DataList = new ObservableCollection<string>();
}
public ObservableCollection<string> iobj_DataList
{
get => grdData.DataSource;
set => grdData.DataSource = value;
}
...
iobj_DataList = new ObservableCollection<string>(myList);
In user control:
public ObservableCollection<string> DataSource
{
...
// This setter runs whenever `grdData.DataSource = value;` runs.
set
{
SetValue(DataSourceProperty, value);
...
}
}
Whenever iobj_DataList is set, its setter sets grdData.DataSource, therefore running DataSource setter. Put whatever logic you need in DataSource setter.
This is plain-old-c#-property-setter logic. Would work even if DataSource was not a BindableProperty.

Xamarin form Command Parameter object is null passing from a template view

I am implementing list view with MVVM and have tap recogniser for the label. I have a custom template for displaying the cell. I am following tutorials on binding the command. I managed to bind the command but could not figure out how to bind Command Property. My command property is always null.
My list view is like this
***** For anyone looking for the solution. Before this line I had a grid view
like below
<Grid x:DataType="viewModels:CartViewModel">
************************************************
<ListView
ItemsSource="{Binding CartItem.Products}"
HasUnevenRows="True"
SeparatorVisibility="None"
VerticalOptions="FillAndExpand"
CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<templates:CartItemTemplate
RemoveItemCommand="{Binding BindingContext.RemoveCartItemCommand, Source={x:Reference Cart}}"
UpdateCartCommandParameter="{Binding .}"
AddCommentCommand="{Binding BindingContext.AddCommentCommand, Source={x:Reference Cart}}"
UpdateCartCommand="{Binding BindingContext.UpdateCartCommand, Source={x:Reference Cart}}"
/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And my template is like this. Other codes are omitted.
<Label
Text="Update cart"
TextDecorations="Underline"
Margin="8, 0, 0, 0"
FontSize="12"
VerticalOptions="Center">
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding UpdateCartCommand}"
CommandParameter="{Binding UpdateCartCommandParameter}"/>
</Label.GestureRecognizers>
</Label>
In the code behind of that template I have done like this
public static readonly BindableProperty UpdateCartCommandProperty =
BindableProperty.Create(nameof(UpdateCartCommand), typeof(ICommand), typeof(CartItemTemplate));
public ICommand UpdateCartCommand
{
get => (ICommand) GetValue(UpdateCartCommandProperty);
set => SetValue(UpdateCartCommandProperty, value);
}
public static BindableProperty UpdateCartCommandParameterProperty =
BindableProperty.Create(nameof(UpdateCartCommandParameter), typeof(Product), typeof(CartItemTemplate));
public Product UpdateCartCommandParameter
{
get => (Product) GetValue(UpdateCartCommandParameterProperty);
set => SetValue(UpdateCartCommandParameterProperty, value);
}
And finally in my MVVM code. I have implemented this.
public ICommand UpdateCartCommand { get; }
private readonly ICartService cartService;
private readonly INavigationService navigationService;
public CartPageViewModel(ICartService cartService, INavigationService navigationService)
{
this.cartService = cartService;
this.navigationService = navigationService;
UpdateCartCommand = new Command<object>(UpdateCartClicked);
}
private async void UpdateCartClicked(object cartItem)
{
await navigationService.ShowAlertAsync("Update Action", "Update cart quantity", "Ok");
}
The problem is object cartItem is always null. What am I doing wrong here? Any idea will be helpful. Thanks
You invoked the line like following which is illegal .
x:DataType="viewModels:CartPageViewModel"
Compiled bindings are currently disabled for any binding expressions that define the Source property. This is because the Source property is always set using the x:Reference markup extension, which can't be resolved at compile time.
If you want to set BindingContext in Xaml , use the following code
<ContentPage.BindingContext>
<viewModels:xxxViewModel/>
</ContentPage.BindingContext>

Xamarin : Date in string

I am new with xamarin, I am facing an issue in my xamarin forms project.
I have a Label inside listview-viewcell, to show time in UI. The date is received as a number like 1510822596449(Java Timestamp). I want to show the date in strings like "n days ago".How can I achieve this?
<StackLayout>
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding createdTime}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Anybody please suggest a solution with working code.
Thanks in advance.
First, create a class DatetimeToStringConverter and add the following code:
public class DatetimeToStringConverter : IValueConverter
{
#region IValueConverter implementation
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return string.Empty;
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
.AddMilliseconds((long)value) // put your value here
.ToLocalTime().ToString("g");
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Then in xaml add the following code at the root of the page:
<ContentPage.Resources>
<ResourceDictionary>
<local:DatetimeToStringConverter x:Key="cnvDateTimeConverter"></local:DatetimeToStringConverter>
</ResourceDictionary>
</ContentPage.Resources>
Then add namespace:
xmlns:local="clr-namespace:Myapp;assembly=Myapp"
Then change the label text like this:
Text="{Binding createdTime, Converter={StaticResource cnvDateTimeConverter}}"
Keep the createTime type as long in model, otherwise you get invalid cast exception.
As suggested in the comments you could do this with a ValueConverter.
Write a converter similar to this, in your shared code.
public class TicksToDateTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!Int64.TryParse(value, out long ticks))
return DatTime.Now;
// TODO you can do a ToString and format it you want here but also in XAML
return new DateTime(ticks);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Not needed
throw new NotImplementedException();
}
}
Now in your XAML, declare te value convter like this under the root of your page, I'm assuming it's a ContentPage.
<ContentPage.Resources>
<ResourceDictionary>
<local:TicksToDateTimeConverter x:Key="TicksConverter" />
</ResourceDictionary>
</ContentPage.Resources>
And don't forget to declare the local namespace in your page root like: xmlns:local="clr-namespace:YourApp.Namespace", which should be the full namespace, without the class name to your converter class.
To finally use the converter in your layout, do this:
<StackLayout>
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding createdTime, Converter={StaticResource TicksConverter}}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Depending on whether or not you return a string from the converter or a DateTime, in the latter case you can also format it here in XAML, like so:
<Label Text="{Binding createdTime, Converter={StaticResource TicksConverter}, StringFormat='{0:dd-MM-yyyy}'}"/>
Or you could choose to do it differently altogether and convert the value inside the model that you bind to the ViewCell.

caliburn micro master detail

I have looked at all the Caliburn Micro stuff I can find and I think I'm simply confusing myself. I put together a simple sample as a test.
Model = Person.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfTestApp
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
PersonView.xaml
<UserControl x:Class="WpfTestApp.PersonView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</Grid>
</UserControl>
ShellViewModel.cs
using Caliburn.Micro;
using System.ComponentModel.Composition;
namespace WpfTestApp {
[Export(typeof(IShell))]
public class ShellViewModel : PropertyChangedBase, IShell
{
public BindableCollection<PersonViewModel> Items { get; set; }
public ShellViewModel()
{
Items = new BindableCollection<PersonViewModel> {
new PersonViewModel(new Person { FirstName="Bart", LastName="Simpson" }),
new PersonViewModel(new Person { FirstName="Lisa", LastName="Simpson" }),
new PersonViewModel(new Person { FirstName="Homer", LastName="Simpson" }),
new PersonViewModel(new Person { FirstName="Marge", LastName="Simpson" }),
new PersonViewModel(new Person { FirstName="Maggie", LastName="Simpson" })
};
}
}
}
ShellView.xaml
<Window x:Class="WpfTestApp.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel>
<ListBox x:Name="Items"/>
</StackPanel>
<ContentControl cal:View.Model="{Binding SelectedItem, Mode=TwoWay}" Grid.Row="1" />
</Grid>
</Window>
I am using the MEFBootstrapper as per the Caliburn Micro documentation.
1) Why is that when I select an item in the ListBox, nothing appears in the ContentControl. I am obviously missing something but I thought SelectedItem was hooked up by the conventions. I've tried using x:Name="ActiveItem" and that did not work either?
2) How does this work if my ShellViewModel.cs contained a BindableCollection of Person instead of PersonViewModel?
3) Can I name the BindableCollection something other than Items (Yes - I know Items is a convention of Caliburn Micro)?
Regards
Alan
Make your ContentControl in ShellView as
<ContentControl Name="ActiveItem"/>
And them inherit your ShellViewModel from Conductor.Collection.OneActive
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive, IShell
{
}
Since Conductor already have Items Property for Binding ChildView, please remove your Items property from ShellViewModel.
Also we have to write code to activate the View in ShellView
Change ListBox to
<ListBox x:Name="Items" cal:Message.Attach="[Event SelectionChanged]=[Action ActivateChildView($this.SelectedItem)]"/>
and inside ShellViewModel a new method
public void ActiveChildView(object view)
{
ActiveItem(view);
}
I have not tested it, but Hope this will work.
You actually need a public property on your view model called SelectedItem (of type PersonViewModel in this case), otherwise there will be nothing to bind to, and you won't be able to access the bound data item from your view model.
You could bind to a collection of Person directly instead. You could say that this breaks the Law of Demeter, but if your PersonViewModel doesn't augment the Person model with any additional data, then you might consider a view model surplus in this case.
Yes you can name it anything, and the conventions will still work, e.g. Users will by convention map to SelectedUser (or ActiveUser or CurrentUser). People won't by convention map to SelectedPerson, but you can alter the ConventionManager Singularize delegate if you wanted this functionality.

Resources