Binding in ItemsControl [duplicate] - c#

This question already has answers here:
"Items collection must be empty before using ItemsSource."
(19 answers)
Closed 2 years ago.
For the life of me I can't get this binding to work.
On the top is my attempt, on the bottom is an example I found online, which works.
<StackPanel Orientation="Horizontal" Height="100" Width="200" Background="White">
<ItemsControl x:Name="shortcutsItems" Width="100" ItemsSource="{Binding}">
Hello world
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="Normal text"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ListView x:Name="shortcutsList" Width="100">
<ListView.View>
<GridView x:Name="gridShortcuts">
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
public partial class ExamShortcuts : UserControl {
public ExamShortcuts() {
InitializeComponent();
ObservableCollection<Shortcut> shortcuts = new ObservableCollection<Shortcut>();
shortcuts = new ObservableCollection<Shortcut>();
shortcutsList.ItemsSource = shortcuts;
shortcutsItems.ItemsSource = shortcuts; // ERROR!!!
//shortcuts.Add(new Shortcut() { Name = "Shortcut Exam 1" });
//shortcuts.Add(new Shortcut() { Name = "Shortcut Exam 2" });
}
}
public class Shortcut {
public string Name { get; set; }
}
When I run, it says "Items collection must be empty before using ItemsSource" for shortcutsItems.ItemsSource = shortcuts;
Note that is IS EMPTY!!!!! And of course it doesn't work when the Add lines are uncommented, either.
I've tried using DataContext = shortcuts and ItemsControl ItemsSource="{Binding]" with the same result.
Also, all examples I've seen have the ItemSource assignment in the code after the items are added anyway!
The shortcutsList works fine.

Remove "Hello World" and the ItemSource binding parts prior to setting the ItemsSource in your codebehind. You are trying to set it dynamically, but have already set it in your xaml which is why it think's the source is not empty. I bet that's causing your issue.
Change
<ItemsControl x:Name="shortcutsItems" Width="100" ItemsSource="{Binding}">
Hello world
to
<ItemsControl x:Name="shortcutsItems" Width="100">

Related

Binding parameter to a nested ListView using SelectedItem

I'm pretty new to coding in c# and I'm trying on a wpf app to transfer data between different folders.
To visualize the folders and subfolders I've got a tabcontrol with different mainfolders, and under each tab a ListView with information on the subfolders.
All data is gathered in a BindingList 'Klinieken', which is filled with objects 'Kliniek' (mainfolders) which contains objects 'Patient' (subfolders) which is filled with information about said folder. Here is the .xaml file for the mainwindow:
<Grid x:Name="LayoutRoot" Background="#555555">
<Grid.RowDefinitions>
<RowDefinition Height="420" />
<RowDefinition Height="116" />
</Grid.RowDefinitions>
<Grid x:Name="KliniekTabs" Background="#555555" Grid.Row="0" Margin="10 10 10 10">
<TabControl ItemsSource="{Binding Klinieken}"
SelectedItem="{Binding BronKliniek}"
TabStripPlacement="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding KliniekNaam}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding Path=Patienten}"
SelectedItem="{Binding Path=SelectedPatient}">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Patientnaam"
Width="250"
DisplayMemberBinding="{Binding PatientNaam}"/>
<GridViewColumn Header="Zisnr"
Width="250"
DisplayMemberBinding="{Binding PatientZis}"/>
<GridViewColumn Header="Aanmaakdatum"
Width="250"
DisplayMemberBinding="{Binding AanmaakDatum}"/>
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
<Grid x:Name="WindowControls" Background="#777777" Grid.Row="1" Margin="100 5 100 10">
<StackPanel Orientation="Vertical">
<Label Margin="-80 0 0 0" HorizontalAlignment="Center">Doel kliniek</Label>
<ComboBox ItemsSource="{Binding Path=Klinieken}"
x:Name="doelKliniekDD"
SelectedItem="{Binding DoelKliniek}"
Margin="-120 0 0 0"
HorizontalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=KliniekNaam}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<StackPanel Orientation="Horizontal" Margin="20 0 0 0" Height="50" VerticalAlignment="Bottom" HorizontalAlignment="Center">
<Button Height="25" Style="{StaticResource Button_Orange}" Command="{Binding TransferButtonCommand}" CommandParameter="{Binding ElementName=window, Mode=OneWay}" Margin="0 0 10 0">Transfer</Button>
<Button Height="25" Style="{StaticResource Button_Orange}" Command="{Binding CopyButtonCommand}" CommandParameter="{Binding ElementName=window, Mode=OneWay}" Margin="10 0 20 0">Kopieer</Button>
<Button Height="25" Style="{StaticResource Button_Grey}" Command="{Binding CloseCommand}" CommandParameter="{Binding ElementName=window, Mode=OneWay}" Margin="20 0 0 0">Cancel</Button>
</StackPanel>
</StackPanel>
</Grid>
</Grid>
And here is the MainViewModel that is referenced in the .xaml:
//attributen
string monacoDirectory;
List<string> aanwezigeClinics;
Installation installation;
//constructor
public MainViewModel()
{
monacoDirectory = ConfigurationManager.AppSettings["monacoDirectory"];
aanwezigeClinics = new List<string>(Directory.GetDirectories(monacoDirectory, "*", SearchOption.TopDirectoryOnly));
aanwezigeClinics.Remove(monacoDirectory + #"\defaults");
aanwezigeClinics.Remove(monacoDirectory + #"\physics");
installation = new Installation(aanwezigeClinics);
Klinieken = new BindingList<Kliniek>();
foreach (var kliniek in installation.Klinieken)
{
Klinieken.Add(kliniek);
}
}
private Kliniek _doelKliniek;
private Kliniek _bronKliniek;
private Patient _selectedPatient;
private BindingList<Kliniek> _klinieken = new BindingList<Kliniek>();
public BindingList<Kliniek> Klinieken
{
get { return _klinieken; }
set { _klinieken = value; }
}
public Kliniek DoelKliniek
{
get { return _doelKliniek; }
set
{
_doelKliniek = value;
OnPropertyChanged();
}
}
public Kliniek BronKliniek
{
get { return _bronKliniek; }
set
{
_bronKliniek = value;
OnPropertyChanged();
}
}
public Patient SelectedPatient
{
get { return _selectedPatient; }
set
{
_selectedPatient = value;
MessageBox.Show("hoi");
OnPropertyChanged();
}
}
The data shows up in the gui just fine, which i was quite happy with. And using SelectedItem on the TabControl and the ComboBox i use later worked out beautifully aswell, but I just cant seem to get the SelectedItem on the ListView to work. I have added a button to test the output of the fields and SelectedPatient always returns as null.
If i check the 'Live Visual Tree' in VS and go to the properties of the ListView i can see the Patient as SelectedItem, so that tells me its not a selection but a binding problem. Furthermore ive tried to google for nested bindings, but the suggestions there didnt change anything about my situation.
Is a nested binding like this possible, or should i take a whole different approach?
Had a similar problem quite a while ago. The suggestions below solved it for me at the time.
Use SelectedValue instead of SelectedItem. This is because SelectedItem doesn't change until the control has been validated. SelectedValue changes whenever the user selects an item. The code you could use is as follows:
SelectedValue="{Binding BronKliniek, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
For the button part, try to provide CommandParameter="{Binding}" instead of CommandParameter="{Binding ElementName=window, Mode=OneWay}". This way, you'll get the value of the current DataContext of the button, which you can then use to extract the data you want from this DataContext.

WPF Combox using Itemteplate with multiple sources

I am trying to make WPF Combobox using ItemTemplate. My idea is to create items in combobox like in this tutorial https://www.wpf-tutorial.com/list-controls/combobox-control/, but slightly different. I have 2 Lists, which I want to use as source. A List containing colors for Rectangles = colorList, and a list containing strings for TextBlocks = classesList
List<System.Windows.Media.SolidColorBrush> colorList = new List<System.Windows.Media.SolidColorBrush>
List<string> classesList = new List<string>
<ComboBox Name="cmbClasses" ItemsSource=" ??? " SelectionChanged="cmbClasses_SelectionChanged" Margin="10" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Name="rectSelectedClassColor" Fill=" ??? " Width="16" Height="16" Margin="0,2,5,2" />
<TextTextBlock Name="cboxSelectedClass" Text=" ??? " MinWidth="50" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Is it possible? How? Thank you.
You should make a new class with a string and Solid color brush as properties
public class NewClass
{
public string Name {get;set;}
public SolidColorBrush Brush {get;set;}
}
Then should make an ObservableCollection of this class and Bind it to your combobox.
ObservableCollection<NewClass> Source = new ObservableCollection<NewClass>();
XAML could look like this.
<ComboBox ItemSource = "{Binding Source}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Rectangle Fill = "{Binding Brush}"/>
<TextBlock Text = "{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Reading Data From WPF ListView. C#

How to read data from a WPF ListView?
Here is my code.
<ListView x:Name="LVR" AllowDrop="True" PreviewDrop="LVR_PreviewDrop" RenderTransformOrigin="0.505,0.506" Margin="0,0,0,0" Grid.Row="1" Grid.ColumnSpan="3" MouseEnter="LVR_MouseEnter" >
<ListView.View>
<GridView >
<GridViewColumn Header="Status" Width="40">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="index.png" Width="26"></Image>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="File Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding TBtxt}" FontWeight="Bold" Foreground="Blue" Cursor="Hand" Height="30" TextAlignment="Left" HorizontalAlignment="Center"></TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
And i am inserting items to the List view like this.
void Insert()
{
WinForms.OpenFileDialog ofd = new WinForms.OpenFileDialog();
ofd.Multiselect = true;
ofd.Title = "Select .TXT File";
ofd.FileName = "";
ofd.Filter = "TXT | *.txt";
if (ofd.ShowDialog() == WinForms.DialogResult.OK)
{
foreach (var filename in ofd.FileNames)
{
if (System.IO.Path.GetExtension(filename).ToUpperInvariant() == ".txt")
{
LVR.Items.Add(new StackItems { TBtxt = filename });
}
}
}
}
class StackItems
{
public string TBtxt { get; set; }
public Image imgg { get; set; }
}
Once I completed adding files, my ListView will looks like this.
|Status | File Name|
|[Image]| test.txt |
|[Image]| test1.txt|
(Sorry. I don't have enough reputation to post image)
Now how will I read the 'File Name' from second column?
I am very new to WPF.
Thanks in advance.
In short, you should be data binding a collection of items (one for each row) to the ListView.ItemsSource property:
<ListView ItemsSource="{Binding SomeCollection}">
<ListView.View>
<!-- Define your view here -->
</ListView.View>
</ListView>
If you do this, then accessing the items is as simple as this (using Linq):
var firstItem = SomeCollection.First();
An improvement on this situation would be to data bind another property of the same type as the objects on the data bound collection to the ListView.SelectedItem property:
<ListView ItemsSource="{Binding SomeCollection}" SelectedItem="{Binding CurrentItem}">
<ListView.View>
<!-- Define your view here -->
</ListView.View>
</ListView>
Doing this will enable you to access properties from the currently selected item from the ListView like this:
int someValue = CurrentItem.SomeProperty;
Please refer to the ListView Class page on MSDN for further help.

WPF - Filling a ListView

I have a 2 column ListView and I'm trying to fill it using and IDictionary with the following code.
System.Collections.IDictionary entryList = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User);
foreach (System.Collections.DictionaryEntry de in entryList)
{
Row row = new Row();
row.Name1 = (string)de.Key;
row.Name2 = (string)de.Value;
this.list1.Items.Add(row);
}
public class Row
{
public string Name1 { get; set; }
public string Name2 { get; set; }
}
XAML:
<ListView x:Name="varList"
Grid.ColumnSpan="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Height="400"
Width="500"
Margin="0, 30, 0, 0">
<ListView.View>
<GridView>
<GridViewColumn Width="150" Header="Name" />
<GridViewColumn Width="350" Header="Path" />
</GridView>
</ListView.View>
</ListView>
But every row and column gets filled with "Project.Views.Row".
Anyone got any idea on how to fix it? Thank you very much.
A ListView (and every other control for that matter) will display the results of calling ToString when given an object to display.
For a standard class, thats its qualified name; Project.Views.Row in your example.
There are two ways to fix this:
Don't add an object. Instead, format the string as you want it ie:
list1.Items.Add(String.Format({0}:{1}, row.Name1, row.Name2));
Do this the right way and use MVVM. In this case your XAML needs a data template:
<ListView ItemsSource="{Binding Rows}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name1}"/>
<TextBlock Text="{Binding Name2}"/>
</StackPanel>
</DataTemplate>
<ListView.ItemTemplate>
</ListView>
For a grid view:
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name1}" />
<GridViewColumn Header="Path" DisplayMemberBinding="{Binding Name2}"/>
</GridView>
</ListView.View>
Binding the ItemsSource is not actually necessary, but since we are doing everything the right way, you should do it so you are not directly manipulating the UI from code.

How do I only show data by filtering the properties in a WPF ListView?

I am working with a WPF control that I created and I am trying to only show certain rows of my list by values of a property. An example is the following, I have a User class that holds a property of Active. How do I tell the .xaml that the list should only show the people that are Active?
Right now I am basically using linq to generate a new list and hand it to the listview based on what I want. However, I would rather just hand the ListView my entire list and let it do the work for me.
Here is my ListView code.
<ListView ItemsSource="{Binding}" DataContext="{Binding }" >
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Index}"/>
<TextBlock Text=". " />
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You'll need some code behind to add a filter:
See: WPF filtering
ICollectionView view = CollectionViewSource.GetDefaultView(lstMovies.ItemsSource);
view.Filter = null;
view.Filter = new Predicate<object>(FilterMovieItem);
private bool FilterMovieItem(object obj)
{
MovieItem item = obj as MovieItem;
if (item == null) return false;
string textFilter = txtFilter.Text;
if (textFilter.Trim().Length == 0) return true; // the filter is empty - pass all items
// apply the filter
if (item.MovieName.ToLower().Contains(textFilter.ToLower())) return true;
return false;
}

Categories