I'm using Xamarin.Forms (C#) and am attempting an MVVM approach.
My classes:
public class Parent
{
public string FirstName { get; set; }
public Child Children { get; set; }
public Parent GetOneParent() {...}
}
public class Child
{
public string FavoriteFruit { get; set; }
}
First of all, what is this type of class called having a "compound" property (i.e. a collection of children)? I don't know what this is referred to so I am limited in "Googling" it.
Ok, I create a single Parent object:
Parent OneParent = new Parent.GetOneParent();
Now I'd like to show in my XAML code:
parent name (in a label)
a list of children's favorite fruits (in a listview since there are multiple)
What's the binding syntax for a label and then a listview for this type of object? {Binding ???}
The answer turned out to be rather simple... the binding context has to be specific to the page and then also more deeply, its controls.
So I did something like this...
In code-behind, I set the binding context:
BindingContext = Parent;
Then in the XAML code...
<StackLayout>
<Label Text="{Binding FirstName}" />
</StackLayout>
<StackLayout>
<!-- Now specify a deeper within the Parent object -->
<ListView x:Name="ParticipantList"
ItemsSource="{Binding Participants}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding FavoriteFruit}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Hope that helps someone down the road!
Related
I have the following combobox in the xaml:
<ComboBox x:Name="cmbCharacters1" HorizontalAlignment="Left" Margin="18,21,0,0" VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32" RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding CharacterEntity}" SelectedItem="{Binding Name}" SelectedValue="{Binding Tag}"/>
and the following class and binding code
public class CharacterEntity
{
public string Name { get; set; }
public string Tag { get; set; }
}
....
cmbCharacters1.ItemsSource = characters;//it is a List<CharacterEntity>
when I run it displays the class name instead of the content of Name property, what am I doing wrong?
I think you forgot to use this: DisplayMemberPath="Tag" Or "Name" whatever you wish to display!
You need to set the DisplayMemberPath in your ComboBox XAML.
This isn't a binding, since the ItemsSource is already bound - you just reference the field name, like so:
<ComboBox DisplayMemberPath="Name" ...
In the XAML you are setting the ItemsSource to a class CharacterEntity instead of List<CharacterEntity>, since you are setting the Itemssource in the code-behind remove it from the XAML and try. Also, you need to set DisplayMemberPath="Name" and set either SelectedItem or SelectedValue not both, if you are using SelectedValue then also use SelectedValuePath="Name"
<ComboBox x:Name="cmbCharacters1" SelectedItem="{Binding someCharacter}" DisplayMemberPath="Name" />
See also this answer: https://stackoverflow.com/a/3797074/424129
C#
public class CharacterEntity
{
public string Name { get; set; }
public string Tag { get; set; }
}
// Look up how to implement INotifyPropertyChanged, I didn't bother here
public class MyViewModel : INotifyPropertyChanged {
public MyViewModel(IEnumerable<CharacterEntity> chars)
{
CharacterEntities = new List<CharacterEntity>(chars);
}
private IEnumerable<CharacterEntity> _characterEntities;
public IEnumerable<CharacterEntity> CharacterEntities {
get { return _characterEntities; }
set { _characterEntities = value;
OnPropertyChanged("CharacterEntities"); }
}
private CharacterEntity _characterEntity
public CharacterEntity SelectedCharacterEntity
}
XAML
ItemsSource is the source for the items. Your binding made no sense. You want to give it a list of CharacterEntity, but you bind to the CharacterEntity class? What list are you talking about? And don't set it in code behind. XAML makes much more sense if you use a viewmodel.
Now, somehow the above MyViewModel class needs to be made the DataContext of some control that owns the ComboBox.
<ComboBox HorizontalAlignment="Left" Margin="18,21,0,0"
VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32"
RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding CharacterEntities}"
SelectedItem="{Binding SelectedCharacterEntity}"
DisplayMemberPath="Name"
/>
When you have it like this:
SelectedItem="{Binding Path=Name}"
it will use what ever is now selected in combobox, that class property of Name is being used as Selected. Without Path, you are binding to that combobox Name object. BUT anyways, this shouldn't yet work in your case with Path. So to have it work as you want it to, try this:
Have a SelectedItem binded to CharacterEntity class:
SelectedItem="{Binding SelectedEntity}" // Class instance of CharacterEntity
And then you have a Text binded to that selected entity class property of Name:
Text="{Binding Path=Name}" // Binded to property of Name
SelectedValue="{Binding Path=Tag}" // Binded to property of Tag
This way it should work. You should have a combobox binded to viewmodel and that viewmodel should have a property(class instance of CharacterEntity) of SelectedEntity. Hopefully this helps:
public class CharacterViewModel
{
public CharacterEntity SelectedEntity {get;set;}
public List<CharacterEntity> characters {get;set;} // use ObservableCollection insteand of List(Automatically update UI if list changes)
}
And XAML:
<ComboBox x:Name="cmbCharacters1" HorizontalAlignment="Left" Margin="18,21,0,0" VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32" RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding characters}" Text="{Binding Path=Name}" SelectedItem="{Binding SelectedEntity}" SelectedValue="{Binding Path=Tag}"/>
Also has in codebehind e.g in constructor:
CharacterViewModel charViewModel = new CharacterViewModel();
cmdCharacters1.DataContext = charViewModel;
cmdCharacters1.ItemsSource= charViewModel.characters;
I'm terrible at explaining this, but I hope it makes sense with my code.
Struggling with showing nested relationship in my TreeView. Here is the scenario:
In the database I have Category and Account tables. Each category can have zero or more sub-categories so this table has a nested relationship with itself. Each category/sub-category can have zero or more Account in it, there is a one-to-many relation between Category and Account. Simple, isn't it!
On top of my DB, I have EDMX, with Categories and Accounts entities in it and their associations as I mentioned above. For ease of understanding, I have renamed navigation properties so that Categories now has ParentCategory, ChildCategories and Accounts properties in it.
On top of EDMX, I have my ViewModel, which defines a public property named AllCategories. My TreeView will bind to this property. I initialize this property at the startup like this:
using (MyEntities context = new MyEntities())
Categories = context.Categories.Include(x => x.Accounts).ToList();
Finally I use the following HierarchicalDataTemplate to show this stuff:
<HierarchicalDataTemplate DataType = "{x:Type local:Category}" ItemsSource = "{Binding Path=ChildCategories}">
<TreeViewItem Header="{Binding Name}" ItemsSource="{Binding Accounts}" />
</HierarchicalDataTemplate>
<DataTemplate DataType = "{x:Type local:Account}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
This runs fine and shows categories, sub-categories and accounts in the tree, but the problem is that sub-categories show up not only under their parent category, but also at the root-level. This happens for categories of all depths. What am I doing wrong here?
Note: If I add .Where(x=>!x.ParentID.HasValue) in the VM, it shows only the root category and its immediate children, nothing else.
Edit
Here's what it currently looks like. Everything goes fine up to the dotted white line (I added that line manually for illustration; has nothing to do with WPF). After that, the sub-categories start repeating with their child sub-categories. This process continues over and over till the leaf sub-categories. I believe I understand what's going on here, but don't have a solution for it. For reference, this guy presents a solution of the problem, but he is using DataSets, whereas I'm working with EF and can't translate his solution into my scenario.
The idea is to connect your business data by ObservableCollections and leave your Hierarchical templates simple, so that the treeview won't show duplicate entries.
The sample code shows nested viewmodel relationship and the corresponding hierarchical templates. For simplification, the Root is an ObservableCollection (otherwise you would need to add INotifyPropertyChanged here and selective ItemsSource Binding in the TreeView)
<Window x:Class="MyWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyWpf"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<HierarchicalDataTemplate DataType = "{x:Type local:RootItem}" ItemsSource = "{Binding Path=Categories}">
<TextBlock Text="{Binding Header}"></TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType = "{x:Type local:CatNode}" ItemsSource = "{Binding Path=Items}">
<TextBlock Text="{Binding Header}"></TextBlock>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding MyRoot}"/>
</Grid>
</Window>
namespace MyWpf
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
MyRoot = new ObservableCollection<RootItem>();
MyRoot.Add(new RootItem());
}
public ObservableCollection<RootItem> MyRoot { get; set; }
}
public class RootItem
{
public RootItem()
{
Categories = new ObservableCollection<CatNode>();
Categories.Add(new CatNode { Header = "Cat1" });
Categories[0].Items.Add("Item11");
Categories[0].Items.Add("Item12");
Categories.Add(new CatNode { Header = "Cat2" });
Categories[1].Items.Add("Item21");
Categories[1].Items.Add("Item22");
}
public string Header { get { return "Root"; }}
public ObservableCollection<CatNode> Categories { get; set; }
}
public class CatNode
{
public CatNode()
{
Items = new ObservableCollection<string>();
}
public string Header { get; set; }
public ObservableCollection<string> Items { get; set; }
}
}
It's a bit strange, but I really can't find a working example anywhere.
By the way, I'm using a ViewModel-first approach (in WPF) if this is important.
Thank you in advance.
If you have a look at the discussion here you will see that the intent of AllActive is to compose several Views/ViewModels into a containing ViewModel. Judging from your previous comments it seems as if this is what you were expecting but I figured I'd at least reference it here.
You then mention activating 3 different ViewModels at different regions of the View. The way I've handled this in the past is to have separate properties for binding/referencing the ViewModels in the View, and then just adding all of them to Items to get the Conductor behavior.
public sealed class MyViewModel : Conductor<Screen>.Collection.AllActive
{
public MyViewModel(IMagicViewModelFactory factory)
{
FirstSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
SecondSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
ThirdSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
Items.Add(FirstSubViewModel);
Items.Add(SecondSubViewModel);
Items.Add(ThirdSubViewModel);
}
public Screen FirstSubViewModel { get; private set; }
public Screen SecondSubViewModel { get; private set; }
public Screen ThirdSubViewModel { get; private set; }
}
And in MyView you would have something like this. Of course you could put these ContentControls wherever you want to in the view.
<StackPanel>
<ContentControl x:Name="FirstSubViewModel" />
<ContentControl x:Name="SecondSubViewModel" />
<ContentControl x:Name="ThirdSubViewModel" />
</StackPanel>
Another common use for AllActive is when you have a list of items. But the items are complex enough to warrant having their own View/ViewModels that require activation. In that case you would not have to have separate properties for the views as you would just set the x:Name of the list control to Items.
You can do implement like below, use TreeViewModel at the place of TabViewModel
ShellView
<DockPanel>
<Button x:Name="OpenTab"
Content="Open Tab"
DockPanel.Dock="Top" />
<TabControl x:Name="Items">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName}" />
<Button Content="X"
cal:Message.Attach="DeactivateItem($dataContext, 'true')" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</DockPanel>
ViewModel
public class ShellViewModel : Conductor<IScreen>.Collection.AllActive {
System.Collections.Generic.List<TabViewModel> tabViewModelCollection = new System.Collections.Generic.List<TabViewModel>();
public void ActiveAllTab() {
foreach (var tabViewModel in tabViewModelCollection)
{
ActivateItem(tabViewModel);
}
}
}
I have a combo box that I have bound to a list that exists in my viewmodel. Now when a users makes a selection in that combo box I want a second combo box to update its content.
So, for example, combobox1 is States and combobox2 should contain only the Zipcodes of that state.
But in my case I don't have a predefined lists before hand for combobox2, I need to go fetch from a db.
Also, if needed, I could get all the potential values for combobox2 (for each combobox1 value) before hand, but I'd like to avoiding that if I can.
How do I implement in WPF and using MVVM? I'm fairly new to this whole wpf\databinding\mvvm world.
Something like the following. Note that the code is drastically simplified for the sake of example. In reality, your ViewModel would implement INotifyPropertyChanged and raise PropertyChanged events when the properties were modified.
The key though is the setter of SelectedState. Your ComboBox would bind its SelectedValue property to the ViewModel's SelectedState property. When the property changed, the ZipCodes collection gets re-loaded which another combobox would be bound to.
class MyViewModel {
public ObservableCollection<string> States {
get;
private set;
}
public ObservableCollection<string> ZipCodes {
get;
private set;
}
public string SelectedState {
get { return _selectedState; }
set {
_selectedState = value;
LoadZipCodes(_selectedState);
}
}
public string SelectedZipCode {
get;
set;
}
void LoadZipCodes(string state) {
// repopulate the ZipCodes property
}
}
Another solution. The approximate model:
class StateViewModel
{
public string StateName
{
get {...}
set {...}
}
public ObservableCollection<ZipCodeViewModel> ZipCodes
{
get {...}
set {...}
}
}
class ZipCodeViewModel
{
public string ZipCodeName
{
get {...}
set {...}
}
}
class MainViewModel
{
public ObservableCollection<StateViewModel> States
{
get {...}
set {...}
}
}
And XAML:
<ComboBox ItemsSource="{Binding Path=States}" IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=StateName}"></Label>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ContentControl Content="{Binding Path=States}">
<ContentControl.ContentTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=ZipCodes}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=ZipCodeName}"></Label>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Assuming the following view model definition:
public class MyObject {
public string Name { get; set; }
}
public interface IMyViewModel {
ICommand MyCommand { get; }
IList<MyObject> MyList { get; }
}
And a UserControl with the following code behind:
public class MyView : UserControl {
public IMyViewModel Model { get; }
}
If my XAML looked like this:
<UserControl>
<ListBox ItemsSource="{Binding MyList}">
<ListBox.ItemTemplate>
<TextBlock Text="{Binding Name}" />
<Button Content="Execute My Command" cmd:Click.Command="{Binding Path=MyCommand, ?????????}" cmd:Click.CommandParameter="{Binding}" />
</ListBox.ItemTemplate>
</ListBox>
How can I bind my Button to the ICommand property of my code-behind class?
I'm using Prism and SL 3.0 and I need to bind each button in my list box to the same command on my view model.
Before my UserControl had a specific name and I was able to use the ElementName binding, but now my UserControl is used multiple times in the same parent view so I can't use that technique anymore and I can't figure out how to do this in XAML.
If it is my only option I can do it manually in the code-behind, but I'd rather do it declaratively in the XAML, if possible.
You need a DataContextProxy for this to work because you're no longer in the context of the UserControl. You've moved out of that and there is no good way to reach back into that context without something like the DataContextProxy. I've used it for my projects and it works great.