XAML ListBox displays class name instead of class property - xaml

Here I have a Listbox configured where the TextBlox in the DataTemplate is set to bind the "Name" Property. But instead it shows the full class name "DomainClasses.Entities.Program". Why?
<Grid DataContext="{Binding _CurrentProgram }">
.....
.....
<ListBox x:Name="ProgramsListBox" Width="600" Height="400" Margin="50,0,50,0" ItemsSource="{Binding _Programs}" VerticalAlignment="Top">
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
<DataTemplate>
</ListBox>
----
----
</Grid>
This is the ViewModel class
public class MainPageViewModel : INotifyPropertyChanged
{
public MainPageViewModel()
{
_currentProgram = new Program();
_Programs = new ObservableCollection<Program>();
}
public async void SaveProgram(bool isEditing)
{
_Programs.Add(_currentProgram);
OnPropertyChanged();
}
private Program _currentProgram;
public Program _CurrentProgram
{
get { return _currentProgram; }
set
{
_currentProgram = value;
OnPropertyChanged();
}
}
private ObservableCollection<Program> _programs;
public ObservableCollection<Program> _Programs
{
get
{
return _programs;
}
set
{
this._programs = value;
OnPropertyChanged();
}
}
// Implement INotifyPropertyChanged Interface
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
}

This is what you need:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Button/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Noticed the ListBox.ItemTemplate around the DataTemplate.
What you have:
<ListBox x:Name="ProgramsListBox" Width="600" Height="400" Margin="50,0,50,0" ItemsSource="{Binding _Programs}" VerticalAlignment="Top">
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
<DataTemplate>
</ListBox>
Creates a ListBox with a DataTemplate as a child (in the same sense that the items in the ItemsSource are children of the ListBox). If I remember correctly, when you set the ItemsSource of a ListBox, all items set in the other fashion are removed. So what you're ending up with is a ListBox with a bunch of Programs in it, which no ItemsTemplate set, so it simply shows the name of the bound class.

You need to add the data template inside listview.itemtemplate and then do the binding. Right now you are adding the data template as a child of the listview.

Related

DataBinding inside data template in UWP

I have a usercontrol as follows which has DataTemplate. I want to bind the data inside the DataTemplate to a property inside the DataContext. In Uwp frustratingly they don't have ancestor type, how can I make my thing to work. I have refered this post UWP Databinding: How to set button command to parent DataContext inside DataTemplate but it doesn't work. Please help.
UserControl:
<local:CommonExpanderUserControl>
<local:CommonExpanderUserControl.ExpanderContent>
<DataTemplate x:DataType="Data:VmInstrumentSettingsLocal">
<StackPanel>
<TextBlock Text="{Binding LisLocalSettings.SomeText}"/>
<controls:ButtonBadged x:Name="ButtonApplyLisLocalChanges" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"
x:Uid="/Application.GlobalizationLibrary/Resources/InstrumentSettingsViewButtonApply"
HorizontalAlignment="Center"
Margin="8"
Command="{Binding LisLocalSettings.SaveLisSettings}"/>
</StackPanel>
</DataTemplate>
</local:CommonExpanderUserControl.ExpanderContent>
</CommonExpanderUserControl>
In my UserControl xaml.cs as follows. I want to bind the button command to Command property inside the LisLocalSettings, but it won't work.
public InstrumentSetupLocalSettingsView()
{
this.InitializeComponent();
DataContext = this;
}
public static readonly DependencyProperty LisLocalSettingsProperty = DependencyProperty.Register(
nameof(LisLocalSettings),
typeof(VmInstrumentSettingsLisLocal),
typeof(InstrumentSetupLocalSettingsView),
new PropertyMetadata(default(VmInstrumentSettingsLisLocal)));
public VmInstrumentSettingsLisLocal LisLocalSettings
{
get => (VmInstrumentSettingsLisLocal) GetValue(LisLocalSettingsProperty);
set => SetValue(LisLocalSettingsProperty, value);
}
DataBinding inside data template in UWP
You could place Command in data source, but if the data source is collection, we need implement multiple command instance. In general, we place the command in current DataContext that could be reused. For the detail steps please refer the following.
<Page.Resources>
<DataTemplate x:Key="HeaderTemplate">
<StackPanel
x:Name="ExpanderHeaderGrid"
Margin="0"
Padding="0"
HorizontalAlignment="Stretch"
Background="Red"
Orientation="Vertical"
>
<TextBlock x:Name="TextBlockLisSharedSettingsTitle" Text="{Binding}" />
<Button Command="{Binding ElementName=RootGrid, Path=DataContext.BtnCommand}" Content="{Binding}" />
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid x:Name="RootGrid">
<uwpControls:Expander Header="hello" HeaderTemplate="{StaticResource HeaderTemplate}" />
</Grid>
Code Behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
public ICommand BtnCommand
{
get
{
return new CommadEventHandler<object>((s) => BtnClick(s));
}
}
private void BtnClick(object s)
{
}
}
public class CommadEventHandler<T> : ICommand
{
public event EventHandler CanExecuteChanged;
public Action<T> action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.action((T)parameter);
}
public CommadEventHandler(Action<T> action)
{
this.action = action;
}
}

Binding IsVisible to property toggled by Switch

I have a Switch bound to a property of an element in a List. I want to bind IsVisible of a button to the same property, but the button's visibility is not changed when the property is changed by the Switch. What am I missing?
XAML:
<StackLayout>
<ListView HorizontalOptions="FillAndExpand" ItemsSource="{Binding EquipmentList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" />
<Switch IsToggled="{Binding State}" />
<Button
Command="{Binding BindingContext.DoCommand, Source={x:Reference TestPage}}"
CommandParameter="{Binding .}"
IsVisible="{Binding State}"
Text="Click" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
ViewModel:
private Command<Equipment> _doCommand;
public Command<Equipment> DoCommand => _doCommand ??
(_doCommand = new Command<Equipment>((Equipment obj) => HandleEquipment(obj)));
// Outputs correct Name and State of the list item
private void HandleEquipment(Equipment obj)
{
System.Diagnostics.Debug.WriteLine(obj.Name + ", " + obj.State);
}
Model:
class Equipment
{
public int Id { get; set; }
public string Name { get; set; }
public bool State { get; set; }
public Equipment(int Id, string Name, bool State)
{
this.Id = Id;
this.Name = Name;
this.State = State;
}
}
As Gerald wrote in his first comment: You have to implement the INotifyPropertyChanged interface on your Equipment model (and not just in the ViewModel).
Without this implementation, the elements in the view have no chance to know, that the state changed (in your case the button).
Implementation:
public class Equipment: INotifyPropertyChanged
{
public bool State
{
get => _state;
set =>
{
_state = value;
OnPropertyChanged();
}
}
private bool _state;
// OTHER PROPERTIES
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The call of the method OnPropertyChanged() is important. The IsVisible property of the button recognizes the change and updates his value.
Instead of binding two things to a property, why not have the single item bound (i.e. the switch) and use XAML to show or hide the button:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibility" />
</Window.Resources>
<StackLayout>
<ListView HorizontalOptions="FillAndExpand" ItemsSource="{Binding EquipmentList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" />
<Switch Name="toggleSwitch" IsToggled="{Binding State}" />
<Button
Command="{Binding BindingContext.DoCommand, Source={x:Reference TestPage}}"
CommandParameter="{Binding .}"
IsVisible="{Binding ElementName=toggleSwitch, Path=IsToggled, Converter={StaticResource BooleanToVisibilityConverter}"
Text="Click" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
It may not be a Window that your StackLayout is in, but if you place a BooleanToVisibilityConverter in your Resources section you'll then be able to use it in your XAML file.
This will mean that if the property name changes in the future you only have one place you need to update in the user interface and you're also using the power of the XAML language.
Also as correctly pointed out by everyone, you need to implement INotifyPropertyChanged in the model in order for the Switch to be updated too.

Passing additional arguments to user control inside the data template

This is the xaml code what i am using
<GridView
Grid.Row="0"
x:Name="RootGrid"
SelectionMode="None"
IsItemClickEnabled="True"
ItemsSource="{Binding RootListSource}">
<GridView.ItemTemplate>
<DataTemplate>
<UserControl:TreeInfoControl/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
In this my user control, it contain another GridView that holds a different IEnumerable collection. What i am trying to achieve is i need to pass this collection through code. I tried this by adding a dependency property to the treecontrol but it is not working. So i am looking for a solution that enable passing the collection through xaml (somehow through the user control). I know it is possible to add that collection to my existing collection and bind that one. But for now i can't use that method.
Here's how you do it.
Start with your App.xaml so we can reuse the demo template
<Application.Resources>
<DataTemplate x:Key="MyContentControl">
<Grid Height="100" Width="100" Background="Maroon">
<TextBlock Text="{Binding FallbackValue=0}" Foreground="White" FontSize="40" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</DataTemplate>
</Application.Resources>
Then we can define your user control
<d:UserControl.DataContext>
<local:MyControlViewModel Number="-1" Letter="~K" />
</d:UserControl.DataContext>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left">
<ContentControl Content="{Binding Number}"
ContentTemplate="{StaticResource MyContentControl}" />
<ListView ItemsSource="{Binding Letters}" IsHitTestVisible="False"
ItemTemplate="{StaticResource MyContentControl}"
SelectedItem="{Binding Letter, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListView>
</StackPanel>
And then we can define your MainPage.xaml
<Page.DataContext>
<local:MainPageViewModel Letter="C" />
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView x:Name="MyList" ItemsSource="{Binding Letters}"
ItemTemplate="{StaticResource MyContentControl}"
SelectedItem="{Binding Letter, Mode=TwoWay}" />
<ListView Grid.Column="1" ItemsSource="{Binding Numbers}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<local:MyControlViewModel
x:Key="MyDataContext" Number="{Binding}"
Letters="{Binding ItemsSource, ElementName=MyList}"
Letter="{Binding SelectedItem, ElementName=MyList}" />
</StackPanel.Resources>
<local:MyControl DataContext="{StaticResource MyDataContext}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Nothing special yet, right? Well, not so fast. We're creating the viewmodel for the user control , setting the properties of the view model from the surrounding scope, then passing it in to the DataContext of the user control explicitly. Cool, huh? Simple enough, if you think about it. Want to set those properties inside the tag? Sure you do. But you can't. The order of operation would be all wrong. You'll just have to trust me.
Now, there's ZERO code behind for your user control. But the view model looks like this:
public class MyControlViewModel : BindableBase
{
public int Number
{
get { return (int)GetValue(NumberProperty); }
set
{
SetValue(NumberProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty NumberProperty =
DependencyProperty.Register("Number", typeof(int), typeof(MyControlViewModel),
new PropertyMetadata(0, (s, e) => { }));
public string Letter
{
get { return (string)GetValue(LetterProperty); }
set
{
SetValue(LetterProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty LetterProperty =
DependencyProperty.Register("Letter", typeof(string), typeof(MyControlViewModel),
new PropertyMetadata("Z", (s, e) => { }));
public ObservableCollection<string> Letters
{
get { return (ObservableCollection<string>)GetValue(LettersProperty); }
set
{
SetValue(LettersProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty LettersProperty =
DependencyProperty.Register("Letters", typeof(ObservableCollection<string>),
typeof(MyControlViewModel),
new PropertyMetadata(new ObservableCollection<string>(new[] { "~W", "~X", "~Y", "~Z" }), (s, e) => { }));
}
All the properties are dependency properties. I hope you noticed. I didn't just do that because I like to type. Though I do like to type. Fact is, I did that because in order to have internal binding you must use a dependency property - and a dependency property that raises property changed! That last part isn't trivial. But does it have to be in a view model? No. But I like it that way.
You might reference this: http://blog.jerrynixon.com/2013/07/solved-two-way-binding-inside-user.html
There's also no code behind for your MainPage. But the view model looks like this:
public class MainPageViewModel : BindableBase
{
public MainPageViewModel()
{
this._Letters = new ObservableCollection<string>(new[] { "A", "B", "C", "D" });
this._Numbers = new ObservableCollection<int>(new[] { 1, 2, 3, 4 });
}
public string Letter
{
get { return (string)GetValue(LetterProperty); }
set
{
SetValue(LetterProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty LetterProperty =
DependencyProperty.Register("Letter", typeof(string), typeof(MyControlViewModel),
new PropertyMetadata("Z", (s, e) => { }));
ObservableCollection<string> _Letters = new ObservableCollection<string>();
public ObservableCollection<string> Letters { get { return _Letters; } }
ObservableCollection<int> _Numbers = new ObservableCollection<int>();
public ObservableCollection<int> Numbers { get { return _Numbers; } }
}
The bindable base is standard, here's the code for it:
public abstract class BindableBase : DependencyObject, System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (!object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
When it's all done, you should get exactly what you want. Something like this:
Not to over-simplify things. But, it's that easy.
Look, getting your head wrapped around XAML is not always easy when you start to nest contexts. I don't blame you for not getting it on first run. But I hope this helps you get started. Keep pushing
Best of luck!

LongListMultiSelector: How to use it with MVVM

I am displaying a list of cities using LongListMultiSelector with Grouping. My ViewModel has DataList propery which is bind to LongListMultiSelector.
On a button click event, i want to remove an item from LongListMultiSelector and also want to update the UI at same time. I don't understand from where should i remove an item so that because of MVVM, UI gets updated automatically.
Below is my CS code.
public class City
{
public string Name { get; set; }
public string Country { get; set; }
public string Language { get; set; }
}
public class Group<T> : List<T>
{
public Group(string name, IEnumerable<T> items)
: base(items)
{
this.Title = name;
}
public string Title
{
get;
set;
}
}
public class myVM : INotifyPropertyChanged
{
static List<City> cityList;
public List<Group<City>> _datalist;
public List<Group<City>> DataList
{
get
{
_datalist = GetCityGroups();
return _datalist;
}
set
{
_datalist = value;
OnPropertyChanged("DataList");
}
}
private static IEnumerable<City> GetCityList()
{
cityList = new List<City>();
cityList.Add(new City() { Name = "Milan", Country = "IT", Language = "Italian" });
cityList.Add(new City() { Name = "Roma", Country = "IT", Language = "Italian" });
cityList.Add(new City() { Name = "Madrid", Country = "ES", Language = "Spanish" });
return cityList;
}
private List<Group<City>> GetCityGroups()
{
IEnumerable<City> cityList = GetCityList();
return GetItemGroups(cityList, c => c.Country);
}
private static List<Group<T>> GetItemGroups<T>(IEnumerable<T> itemList, Func<T, string> getKeyFunc)
{
IEnumerable<Group<T>> groupList = from item in itemList
group item by getKeyFunc(item) into g
orderby g.Key
select new Group<T>(g.Key, g);
return groupList.ToList();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Below is my XAML code
<Button Content="bind" Width="150" Height="150" VerticalAlignment="Bottom" Click="Button_Click"></Button>
<toolkit:LongListMultiSelector x:Name="AddrBook"
ItemsSource="{Binding DataList}"
EnforceIsSelectionEnabled="True"
JumpListStyle="{StaticResource AddrBookJumpListStyle}"
IsSelectionEnabled="True"
Background="Transparent"
GroupHeaderTemplate="{StaticResource AddrBookGroupHeaderTemplate}"
ItemTemplate="{StaticResource AddrBookItemTemplate}"
LayoutMode="List"
IsGroupingEnabled="true"
HideEmptyGroups ="true"/>
In phone:PhoneApplicationPage.Resources i have below xaml
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="AddrBookItemTemplate">
<StackPanel VerticalAlignment="Top">
<TextBlock Text="{Binding Name, Mode=TwoWay}" />
<TextBlock Text="{Binding Language, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="AddrBookGroupHeaderTemplate">
<Border Background="Transparent" Margin="12,8,0,8">
<Border Background="{StaticResource PhoneAccentBrush}"
Padding="8,0,0,0" Width="62" Height="62"
HorizontalAlignment="Left">
<TextBlock Text="{Binding Title, Mode=TwoWay}" Foreground="{StaticResource PhoneForegroundBrush}" FontSize="48" Padding="6"
FontFamily="{StaticResource PhoneFontFamilySemiLight}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Border>
</Border>
</DataTemplate>
<phone:JumpListItemBackgroundConverter x:Key="BackgroundConverter"/>
<phone:JumpListItemForegroundConverter x:Key="ForegroundConverter"/>
<Style x:Key="AddrBookJumpListStyle" TargetType="phone:LongListSelector">
<Setter Property="GridCellSize" Value="113,113"/>
<Setter Property="LayoutMode" Value="Grid" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border Background="{Binding Converter={StaticResource BackgroundConverter}}" Width="Auto" Height="Auto" Margin="6" >
<TextBlock Text="{Binding Title, Mode=TwoWay}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" FontSize="48" Padding="6"
Margin="8,0,0,0" Foreground="{Binding Converter={StaticResource ForegroundConverter}}" VerticalAlignment="Bottom"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</phone:PhoneApplicationPage.Resources>
You simply remove the item from your DataList inside your VM.
The tricky part is to handle the SelectedItems of the MultiSelector since it isnt bindable.
The simplest solution for me was to hook up a command to the SelectionChanged event and pass the SelectedItems as a parameter with it (I used the Command class from the MvvmLight Toolkit for that). Inside the Command I check for any changes between the updated List and the old List in the VM.
Also you shouldn't use the Click Event on the button, in MVVM the Command Property is used along with the CommandParameter if needed.
For other Controls that dont have a build-in Command Property you can use something like the aforementioned class from the toolkit (or other MVVM frameworks).
Other things to notice:
You need to use something like an ObservableCollection instead of a List if you want the UI to automatically update after changes to the collection.
Also you cant actually remove anything from your DataList since your always re-reading your hardcoded items.

Get SelectedItem from ListBox

I want to get the SelectedItem from a ListBox which looks like this inside my Windows 8 Store App:
<ListBox x:Name="listBox" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" Foreground="Black" BorderThickness="0" Background="#FFD8D8D8" />
The problem is, that the ListBox don't fire the SelectedItem propertie. I have to use IsSynchronizedWithCurrentItem="True" but then an error appears which says the true isn't supportet for this property. What do I have to do or are there any other ways to get the SelectedItem propertie?
I have this Code behind:
namespace ExampleApp
{
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private object currentItem;
//Constructor and so on
public object SelectedItem
{
get { Debug.WriteLine("get"); return currentItem; }
set { Debug.WriteLine("set"); currentItem = value; NotifyPropertyChanged(); }
}
}
}
you should try this
<ListBox x:Name="listBox" SelectedItem="{Binding ElementName=YourPageName,path=DataContext.SelectedItem, Mode=TwoWay}" Foreground="Black" BorderThickness="0" Background="#FFD8D8D8" />