WPF ListView ItemTemplate Cannot set properties on property elements - c#

I am working on ListView in WPF, I want the ListView to get ItemLists from a ViewModel but I am getting the following error
Cannot set properties on property elements
xaml code:
<ListView ItemsSource="{Binding MenuItems}" Name="mainSideMenuList" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Background="#FF284593" Foreground="#FF3457D1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction
Command="{Binding Command}"
CommandParameter="{Binding ElementName=mainSideMenuList, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.ItemTemplate Height="60">
<DataTemplate>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="{Binding IconKind}" Width="25" Height="25" Margin="10" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" Margin="10 10" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel:
private readonly ItemHandler _itemHandler;
private ICommand _command;
public MainWindowViewModel() {
_itemHandler = new ItemHandler();
_itemHandler.Add(new Item("Settings", PackIconKind.Settings));
_itemHandler.Add(new Item("Products", PackIconKind.FoodForkDrink));
_itemHandler.Add(new Item("Tickets", PackIconKind.Ticket));
_itemHandler.Add(new Item("Entities", PackIconKind.Table));
_itemHandler.Add(new Item("Accounts", PackIconKind.Calculator));
_itemHandler.Add(new Item("Inventory", PackIconKind.Database));
_itemHandler.Add(new Item("Printing", PackIconKind.Printer));
_itemHandler.Add(new Item("Reports", PackIconKind.FileAccount));
_itemHandler.Add(new Item("Automation", PackIconKind.Calculator));
_itemHandler.Add(new Item("Users", PackIconKind.User));
}
public List < Item > MenuItems {
get {
return _itemHandler.MenuItems;
}
}
public ICommand Command {
get {
return _command ? ? (_command = new RelayCommand(x => {
DoStuff(x as Item);
}));
}
}
private void DoStuff(Item item) {
MessageBox.Show(item.Name + " element clicked");
}
What am I doing wrong and how can I resolve?

The problem is that the ListView.ItemTemplate is of type DataTemplate which is not a FrameworkElement so it can not have any Height or Widthor anything else which has to do with UI-Elements. It can only have one Content and this is a DataTemplate which can hold one FrameworkElement, mostly a Panel. These element then can have a height or width or any UI-Element property.
Therefore remove the Height of <ListView.ItemTemplate Height="60"> and set it to the <StackPanel Orientation="Horizontal">

Related

ListVIew of ItemsControl: Get the ListViewItem index after selecting one its item from ItemsControl

I have a ListView. Inside of this ListView there is ItemsControl and inside it there is second ItemsControl. Inside of the second ItemsControl there are TextBoxes.
ListView -> ItemsControl -> ItemsControl -> TextBox
Is there any chance that I would be able to get index of ListViewItem, which specific TextBox belongs to after clicking on this TextBox?
For example
I select a ListViewItem on index 0 but then I click on TextBox which belong to ListViewItem on index 2. In that case I would like to change value of SelectedGroupIndex from 0 to 2.
"Hello" strings are just for testing.
Thank you very much.
ViewModel
public class MainWindowViewModel
{
public ObservableCollection<ObservableCollection<ObservableCollection<ListViewString>>> AllTexts { get; set; }
public int SelectedGroupIndex { get; set; }
public ICommand AddGroup { get; private set; }
public ICommand AddColumn { get; private set; }
public ICommand TextBoxSelected { get; private set; }
public MainWindowViewModel()
{
this.AllTexts = new ObservableCollection<ObservableCollection<ObservableCollection<ListViewString>>>();
this.SelectedGroupIndex = -1;
this.AddGroup = new Command(this.AddGroupCommandHandler);
this.AddColumn = new Command(this.AddColumnCommandHandler);
this.TextBoxSelected = new Command(this.TextBoxSelectedCommandHandler);
}
private void AddGroupCommandHandler()
{
var tempColumn = new ObservableCollection<ListViewString>() {
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello") };
var tempGroup = new ObservableCollection<ObservableCollection<ListViewString>>();
tempGroup.Add(tempColumn);
this.AllTexts.Add(new ObservableCollection<ObservableCollection<ListViewString>>(tempGroup));
}
private void AddColumnCommandHandler()
{
if (this.SelectedGroupIndex >= 0 && this.SelectedGroupIndex < this.AllTexts.Count)
{
var tempColumn = new ObservableCollection<ListViewString>() {
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello") };
this.AllTexts[this.SelectedGroupIndex].Add(tempColumn);
}
}
private void TextBoxSelectedCommandHandler()
{
// TODO: Change SelectedItem of ListView
// this.SelectedGroupIndex = ...;
}
private ListViewString GetListViewString(string text)
{
return new ListViewString { Value = text };
}
private string GetTextFromListViewString(ListViewString listViewString)
{
return listViewString.Value;
}
}
/// <summary>
/// Class used to show user Text in ListView.
/// Using this class fixes the issue that ObservableCollection didn't update
/// after user changed values of TextBoxes in GUI.
/// </summary>
public class ListViewString : DependencyObject
{
public string Value
{
get
{
return (string)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(ListViewString), new PropertyMetadata(string.Empty));
}
View:
<Window.Resources>
<ResourceDictionary>
<local:MainWindowViewModel x:Key="vm" />
</ResourceDictionary>
</Window.Resources>
<Grid Margin="10,10,10,10" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="300" />
<RowDefinition />
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemsSource="{Binding AllTexts, Source={StaticResource vm}, Mode=TwoWay}"
Background="Blue"
SelectedIndex="{Binding SelectedGroupIndex, Source={StaticResource vm}}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value}"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
Width="100" Height="40">
<TextBox.InputBindings>
<MouseBinding Gesture="LeftClick"
Command="{Binding TextBoxSelected, Source={StaticResource vm}}" />
</TextBox.InputBindings>
</TextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,20,0,0">
<Button Content="Add Group" Width="120" Height="30"
Command="{Binding AddGroup, Source={StaticResource vm}}" />
<Button Content="Add Column" Margin="20,0,0,0" Width="120" Height="30"
Command="{Binding AddColumn, Source={StaticResource vm}}" />
<TextBlock Width="120" Height="30" FontSize="20" Margin="20,0,0,0"
Text="{Binding SelectedGroupIndex, Source={StaticResource vm}}" />
</StackPanel>
</Grid>
Actually what you need is to pass the DataContext of your TextBox to the command as parameter, so use CommandParameter for it and implement your command with parameter:
<TextBox.InputBindings>
<MouseBinding Gesture="LeftClick"
Command="{Binding TextBoxSelected, Source={StaticResource vm}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Mode=Self}}"/>
</TextBox.InputBindings>
So you will have an item from your items source collection as command parameter and can find the index of it.

How update ListBox when ItemsSource changed

I have buttons "ADD" and "DEL", but "DEL" does not work. What is wrong?
count in my ObservableCollection<User> was changed but ListBox does not
sample project: https://github.com/Veselov-Dmitry/MyQuestion
view:
<StackPanel>
<Button Content="ADD"
Command="{Binding AddUsers_OASUCommand}"
CommandParameter="">
</Button>
<ListBox ItemsSource="{Binding Users_OASU}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Login}" />
<Button Content="DEL"
Command="{Binding DelUsers_OASUCommand}"
CommandParameter="{Binding Path=Content,
RelativeSource={RelativeSource Mode=FindAncestor ,
AncestorType={x:Type ListBoxItem}}}">
<Button.DataContext>
<local:ViewModel />
</Button.DataContext>
</Button>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
I set datacontext in constructor MainView
viewvmodel:
class ViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<User> Users_OASU{get; set;}
public ICommand AddUsers_OASUCommand{get; set;}
public ICommand DelUsers_OASUCommand{get; set;}
public ViewModel()
{
Users_OASU = new ObservableCollection<User>(GetUsers());
AddUsers_OASUCommand = new Command<object>(arg => AddUsers_OASUMethod());
DelUsers_OASUCommand = new Command<object>(arg => DelUsers_OASUMethod(arg));
}
private void DelUsers_OASUMethod(object arg)
{
User find = Users_OASU.Where(x => x.Login == (arg as User).Login).FirstOrDefault();
Users_OASU.Remove(find);
}
private void AddUsers_OASUMethod()
{
Users_OASU.Add(new User("52221", "John X."));
}
private List<User> GetUsers()
{
List<User> list = new List<User>();
list.Add(new User("52222", "John W."));
list.Add(new User("52223", "John Z."));
list.Add(new User("52224", "John A."));
list.Add(new User("52225", "John M."));
return list;
}
}
"count in my ObservableCollection was changed but ListBox does not" - you have multiple instances of ViewModel, count was changed, but not in the collection which is displayed
you need to setup DataTemplate correctly to avoid that
first, each Button will get User object for DataContext (it will be provided by ListBox from ItemsSource). You mustn't declare new <Button.DataContext>
second, DelUsers_OASUCommand is declared in a ViewModel class, it is accessible on ListBox level, from DataContext. Change binding path accordingly.
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Login}" />
<Button Command="{Binding DataContext.DelUsers_OASUCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding Path=Content,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Content="DEL" />
</WrapPanel>
</DataTemplate>
additionally I would change DelUsers_OASUMethod to accept User as argument
private void DelUsers_OASUMethod(object arg)
{
Users_OASU.Remove(arg as User);
}
and pass CommandParameter like this:
CommandParameter="{Binding Path=.}"
or the same, but shorter:
CommandParameter="{Binding}"

Initial Focus on selected ListViewItem

I have a simple dialog with MVVM, just a ListView and two Buttons (Ok, Cancel) and a ViewModel responsible for binding the ItemsSource and SelectedItem of the ListView.
View:
...
<StackPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ListView ItemsSource="{Binding Projects}" SelectedItem="{Binding Project}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
<StackPanel Orientation="Horizontal" FlowDirection="RightToLeft">
<Button Content="Cancel" IsCancel="True"/>
<Button Content="Ok" Command="{Binding OkCommand}" CommandParameter="{Binding ElementName=Dlg}"/>
</StackPanel>
</StackPanel>
...
Viewmodel:
public class SelectProjectViewModel : ViewModelBase, IModalDialogViewModel
{
private readonly ProjectList _projectList;
public ProjectList Projects => _projectList;
public Project Project
{
get { return GetValue(() => Project); }
set { SetValue(() => Project, value);}
}
private RelayCommand _okCommand;
public RelayCommand OkCommand => _okCommand ?? (_okCommand = new RelayCommand(OkCommandCall));
public SelectProjectViewModel(ProjectList projectList, Project currentProject)
{
_projectList = projectList;
this.Project = currentProject;
}
private void OkCommandCall(object window)
{
DialogResult = true;
WindowCloseBehaviour.SetClose(window as DependencyObject, true);
}
public bool? DialogResult
{
get { return GetValue(() => DialogResult); }
private set { SetValue(() => DialogResult, value); }
}
}
I tried to achieve focusing the first Element like described here, with the accepted solution and the equivalent behaviour class. But this always sets the focus on the Cancel-Button, while I want my ListView to be focused when opening the dialog, so that the selected element is really highlighted, not just in the "inactive highlight" color. And no, I don't want to workaround with changing the colors.
Using simple FocusedElement on a parent with SelectedIndex on ListView itself works for me:
<StackPanel FocusManager.FocusedElement="{Binding ElementName=myListView}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ListView Name="myListView" ItemsSource="{Binding Projects}" SelectedItem="{Binding Project}" SelectionMode="Single"
SelectedIndex="0">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
...
</StackPanel>
</StackPanel>

Get index of item in bindable collection

In this listbox i display contact names.
<ListBox x:Name="Items" Margin="36,38,78,131">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="lol" Text="{Binding Path=ContactName}" Style="{StaticResource PhoneTextSmallStyle}"
Width="Auto" TextAlignment="Center" FontWeight="Bold" Foreground="White" VerticalAlignment="Bottom" TextWrapping="Wrap"/>
<Button x:Name="ShowName">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="delete" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I get contacts from local DB
public List<FBContacts> listContactDatas { get; set; }
Items = new BindableCollection<FBContacts>();= new BindableCollection<FBContacts>();
public void GetContacts()
{
using(MyDataContext mydb = new MyDataContext(DBConnectionstring))
{
var items = from ContactsList Name in mydb._contacts select Name;
foreach (var toDoItem in items)
{
Items.Add(new FBContacts()
{
ContactName = toDoItem.Name
});
}
}
}
user can delete any contact if he press button.
public void delete()
{
Items.RemoveAt(/* index*/);
}
so how i can get index of choosen contact?
It is easier if you pass the clicked FBContacts to delete method :
<Button x:Name="ShowName">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="delete">
<cal:Parameter Value="{Binding}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Then you can remove by FBContacts object instead of index :
public void delete(FBContacts item)
{
Items.Remove(item);
}
Bind the currently selected item's index to a separate property:
<ListBox x:Name="Items" SelectedIndex="{Binding SelectedListIndex}" Margin="36,38,78,131">
Of course, SelectedListIndex must be defined as property of type int that fires PropertyChanged in the Viewmodel.
Then, you can easily access the selected item's index everywhere within the Viewmodel:
public void delete()
{
Items.RemoveAt(SelectedListIndex);
}

Retrieve View info from DataTemplateSelector

maybe this is a trivial question for many of you...
My app has a TabControl defined as:
<TabControl ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}">
<!--Bind the SelectionChanged event of the tab-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<!--This is How tab will look-->
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<Button Name="BtnCloseTab"
DockPanel.Dock="Right"
Margin="5,0,0,0"
Padding="0"
Command="{Binding RelativeSource=
{RelativeSource FindAncestor, AncestorType={x:Type TabControl}},
Path=DataContext.CloseTabCommand}">
<Image Source="/EurocomCPS;component/Images/closeTab.png" Height="11" Width="11"></Image>
</Button>
<TextBlock Text="{Binding Header}" />
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<!--This will be the content for the tab control-->
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl
ContentTemplateSelector="{StaticResource TemplateSelector}"
Content="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
In the window ViewModel I have the following prop:
private ObservableCollection<Tab> _Tabs;
public CPSViewModel()
{
_Tabs = new ObservableCollection<Tab>();
}
public ObservableCollection<Tab> Tabs
{
get { return _Tabs;}
private set
{
_Tabs = value;
this.RaisePropertyChanged("Tabs");
}
}
Now, when a new Tab is created, the following DataTemplateSelector is called:
class TemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item != null)
{
string templateFile = string.Format("Templates/{0}",
Properties.Settings.Default.AppId + ".tmpl");
if (File.Exists(templateFile))
{
FileStream fs = new FileStream(templateFile, FileMode.Open);
DataTemplate template = XamlReader.Load(fs) as DataTemplate;
return template;
}
}
return null;
}
}
The DataTemplate is based on the XmlDataProvider and here I need to "inform" the Template which xml file it has to load because it is different for every tab:
<DataTemplate
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate.Resources>
<local:StringToBoolConverter x:Key="StringToBoolConverter" />
<local:StringToIntConverter x:Key="StringToIntConverter" />
<XmlDataProvider x:Key="dataProvider" XPath="func/parametri/param/BLOCKS"/>
</DataTemplate.Resources>
<Grid>
.... controls ....
</Grid>
</DataTemplate>
Is there a way to do it?
EDIT
Substantially what I have to do is to have access to my Tab class into the TemplateSelector.
Regards,
Daniele.
if you could define your tabs like
public class TabFirst:ITab {}
public class TabSecond:ITab {}
public class TabBlup:ITab {}
viewmodel
public ObservableCollection<ITab> Tabs
{
get { return _Tabs;}
private set
{
_Tabs = value;
this.RaisePropertyChanged("Tabs");
}
}
you could get rid of the DataTemplateSelector and just definfe your datatemplates in your resources
<DataTemplate DataType="{x:Type local:TabFirst}">
<view:TabFirstView />
<DataTemplate/>
<DataTemplate DataType="{x:Type local:TabSecond}">
<view:TabSecondView />
<DataTemplate/>
and your content control would be just
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>

Categories

Resources