How are ListView's ItemCollections related? - c#

If I create multiple ListViews with the same ItemsSource they become strangely linked. In the following example, the two ListViews display a common list of strings. The assertions show that the two ItemCollections and SortDescriptionCollections are distinct, but if I attempt to sort the ListViews differently, the second sort order is applied to both.
The two ItemCollections must be related in order for the Selector.IsSynchronizedWithCurrentItem property to have any effect, but I would like to be able to break this association so that I can do things like I've tried in this example. Does anyone know how these collections are related, and how I can sever this relationship?
XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:llv="clr-namespace:LinkedListViews"
x:Class="LinkedListViews.Window1"
x:Name="Window"
Title="Window1"
Width="640" Height="480">
<Grid x:Name="LayoutRoot">
<ListView
x:Name="ListView1"
ItemsSource="{Binding ElementName=Window, Path=Data}"
Margin="75,8,0,8" Width="237" HorizontalAlignment="Left"/>
<ListView
x:Name="ListView2"
ItemsSource="{Binding ElementName=Window, Path=Data}"
HorizontalAlignment="Right" Margin="0,8,73,8" Width="243"/>
</Grid>
</Window>
Code behind:
using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.ComponentModel;
using System.Collections.Generic;
namespace LinkedListViews
{
public partial class Window1
{
private List<string> _Data = new List<string>
{
"Alpha", "Beta", "Gamma"
};
public List<string> Data
{
get { return _Data; }
}
public Window1()
{
this.InitializeComponent();
// Insert code required on object creation below this point.
System.Diagnostics.Debug.Assert(ListView1.Items != ListView2.Items);
System.Diagnostics.Debug.Assert(ListView1.Items.SortDescriptions != ListView2.Items.SortDescriptions);
this.ListView1.Items.SortDescriptions.Add(new SortDescription(null, ListSortDirection.Ascending));
this.ListView2.Items.SortDescriptions.Clear();
this.ListView2.Items.SortDescriptions.Add(new SortDescription(null, ListSortDirection.Descending));
}
}
}

See Stopping ItemsControls from sharing filters

Related

Binding a list/collection/IEnumerable of Model Class to a Data Grid and Selecting Columns

I can't seem to find this answer via searching and cannot connect to the dots from other examples to my scenario.
Using MVVM Pattern:
How do I bind my model class via ViewModel in my view to a listbox/datagrid?
How do I pick and choose which columns I want displayed?
Question 2 Followup: Can I pick and choose via the visual Studio UI properties, if yes, how?
If done with XAML, is there autocomplete with my model/viewmodel class?
Using Observable Collection, what's the correct way to implement?
What is the "preferred" XAML control(datagrid?, listbox?, etc )
I've simplified my example into this:
Model Class:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace TestListBinding.Models
{
public class ProjectModel
{
public string Id { get; set; }
public string ProjectTitle { get; set; }
public string ProjectDetails { get; set; }
}
}
ViewModel Class:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Threading.Tasks;
using TestListBinding.Models;
namespace TestListBinding.ViewModels
{
public class ProjectViewModel
{
private ProjectModel _project;
private ObservableCollection<ProjectModel> _projects;
public ProjectViewModel()
{
_projects = new ObservableCollection<ProjectModel>();
var proj = new ProjectModel();
for (int i = 1; i <= 3; i++)
{
proj.Id = "ID" + i.ToString();
proj.ProjectTitle = "Project " + i.ToString();
proj.ProjectDetails = "Details about this: " + i.ToString();
_projects.Add(proj);
proj = new ProjectModel();
}
}
public IEnumerable<ProjectModel> Projects
{
get { return _projects; }
}
}
}
The View Part:
I know I need to have a DataContext for the view. Many examples show it being set in the code-behind(see below), and some example reference a "StaticResource" for a listbox. I've not found a way to setup the bindings via UI with the way my code is setup.
MainWindow.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using TestListBinding.ViewModels;
namespace TestListBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ProjectViewModel();//is this correct, or is there an alternative method via XAML/control properties?
}
}
}
XAML(via code or UI) Can you please help me complete the data grid columns/bindings:
Updated per #EdPlunkett
<Window x:Class="TestListBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestListBinding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid HorizontalAlignment="Left" Height="239" Margin="42,32,0,0" VerticalAlignment="Top" Width="435" ItemsSource="{Binding Projects}">
<DataGrid.Columns>
<DataGridTextColumn Header="Project Title" Binding="{Binding ProjectTitle}"/>
<DataGridTextColumn Header="Project Details" Binding="{Binding ProjectDetails}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
This yields a strange layout with extra columns:
This is correct in your window constructor:
DataContext = new ProjectViewModel();
And this should show you some stuff in the grid:
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" Header="ID" />
<DataGridTextColumn Binding="{Binding ProjectTitle}" Header="Project Title" />
<DataGridTextColumn Binding="{Binding ProjectDetails}" Header="Project Details" />
</DataGrid.Columns>
WPF people generally don’t use the designer/Properties pane stuff. It’s not great and it hasn’t improved in over ten years. You’re out of luck on autocomplete in this case as well.
Preferred control? Depends what you want: If you want multiple columns per row, use DataGrid or ListView. If you want one value displayed per row, or some kind of pretty templated UI per row, use ListBox.
Quick ListBox:
<ListBox
ItemsSource=“{Binding Projects}”
DisplayMemberPath=“ProjectTitle”
/>

Get rid of Visual Studio Namespace error message

this is unfortunately a very beginner question:
I am doing the very simple WPF tutorials and I am stuck on a namespace problem.
I want to do a simple hierarchical treeview binding on a custom object according to the tutorial. I put the object into a custom namespace "MyNameSpace" and declared this in XAML ( xmlns:MyTree="clr-namespace:MyNameSpace"). I believe I don't need to specify the assembly as I am just in my project without any further reference (new and clean project).
The problem I have now, is that the compiler gives me an error at
<HierarchicalDataTemplate DataType="{x:Type MyTree:MenuItemNew}"
with the message
The name "MenuItemNew" does not exist in the namespace "clr-namespace:MyNameSpace"
But it does exist! AND it even compiles and starts the program correctly. However, I cannot see the layout anymore because of "Invalid Markup".
So how can I tell XAML to accept my namespace? Or what would be a best way to solve this?
Here is the XAML:
<Window x:Class="TreeViewTestC.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:MyTree="clr-namespace:MyNameSpace"
Title="MainWindow" Height="350" Width="525">
<Grid Margin="10">
<TreeView Name="trvMenu">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type MyTree:MenuItemNew}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Title}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
and here is my MainWindow Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using System.Collections.ObjectModel;
using MyNameSpace;
namespace TreeViewTestC
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MenuItemNew root = new MenuItemNew() { Title = "Menu1" };
MenuItemNew childItem1 = new MenuItemNew() { Title = "Child item #1" };
childItem1.Items.Add(new MenuItemNew() { Title = "Child item #1.1" });
childItem1.Items.Add(new MenuItemNew() { Title = "Child item #1.2" });
root.Items.Add(childItem1);
root.Items.Add(new MenuItemNew() { Title = "Child item #2" });
trvMenu.Items.Add(root);
}
}
}
namespace MyNameSpace
{
public class MenuItemNew
{
public MenuItemNew()
{
this.Items = new ObservableCollection<MenuItemNew>();
}
public string Title { get; set; }
public ObservableCollection<MenuItemNew> Items { get; set; }
}
}
It seems to me that your main application namespace is TreeViewTestC and not MyNameSpace. Therefore, you may well need to tell the compiler that your MyNameSpace is actually in the TreeViewTestC assembly, despite you only having a single project. Try using this instead:
xmlns:MyTree="clr-namespace:MyNameSpace;assembly=TreeViewTestC"
Ok, I found the solution. The project was on a network drive. Moving it to a local folder solves the issues...
Man, what a mess. This has cost me so much time and I need the network drive..
More info:
The name "XYZ" does not exist in the namespace "clr-namespace:ABC"
Thanks a lot for your help

wpf using DataTemplate into HierachicalDataTemplate

in Wpf application i've to show the same collection of items into a ListBox and TreeView, The List show only the first level, the TreeView the whole Hierarchical. My Question is: why in the TreeView is not applied the DataTemplate i declared for Item object? how do i can share the same DataTemplate across the 2 controls (the color of text into TV must be red)?
<Window x:Class="TestWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:viewmodel="clr-namespace:TestWpf"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.Resources>
<DataTemplate DataType="{x:Type viewmodel:Item}">
<TextBlock Foreground="Red" Text="{Binding Caption}"></TextBlock>
</DataTemplate>
</Grid.Resources>
<ListBox Grid.Row="0" Name="lb1" ></ListBox>
<TreeView Name="tv1" Grid.Row="1" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type viewmodel:Item}" ItemsSource="{Binding Items}" >
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
MainWindow.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace TestWpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<Item> Items = new ObservableCollection<Item>();
var item1 = new Item("1");
Items.Add(item1);
item1.Items.Add(new Item("1.1"));
item1.Items.Add(new Item("1.2"));
item1.Items.Add(new Item("1.3"));
var item2 = new Item("2");
Items.Add(item2);
item2.Items.Add(new Item("2.1"));
item2.Items.Add(new Item("2.2"));
item2.Items.Add(new Item("2.3"));
var item3 = new Item("3");
Items.Add(item3);
item3.Items.Add(new Item("3.1"));
item3.Items.Add(new Item("3.2"));
item3.Items.Add(new Item("3.3"));
this.lb1.ItemsSource = Items;
this.tv1.ItemsSource = Items;
}
}
}
Item.cs
namespace TestWpf
{
public class Item
{
public List<Item> Items { get; set; }
public string Caption { get; set; }
public Item(string caption)
{
this.Caption = caption;
this.Items = new List<Item>();
}
}
}
I just copy and pasted your code in VS, pressed F5 and it worked.
The only thing i had to change was the namespace since i created it with WPFApplication1 :)
Try taking your code out to a fresh project and try to build it and see if it works.

List<string> is not updated back to ListBox's ItemsSource?

I'm new to WPF. I have a List<string> as a source for my ListBox's ItemsSource. Initially, the ListBox shows all the Items in my List<string> OK. However, after trying adding some string to my List<string>, the ListBox doesn't update the changes. I'm using Binding to bind the data (behind) with the ListBox (view), here is my code:
//Code behind
public MainWindow: Window {
public MainWindow(){
InitializeComponent();
Items = new List<string>(){"1","2","3"};//after loaded, all these values are displayed OK in my ListBox.
DataContext = this;
//Try clicking on a button to add new value
button1.Click += (s,e) => {
Items.Add("4");//But my ListBox stays the same without any update/changes.
};
}
public List<string> Items {get;set;}
}
//XAML
<ListBox ItemsSource={Binding Items}/>
Could you please point out what I'm doing wrong here and give me a solution? Thank you very much in advance.
If you had read the documentation of ItemsSource you would already know what is wrong.
[...]
This example shows how to create and bind to a collection that derives from the ObservableCollection<T> class, which is a collection class that provides notifications when items get added or removed.
you should try ObservableCollection instead because it's
Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Click="Button_Click" Content="Button" HorizontalAlignment="Left" Margin="441,289,0,0" VerticalAlignment="Top" Width="75"/>
<ListBox HorizontalAlignment="Left" ItemsSource="{Binding MyList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="lstbox" Height="296" Margin="21,23,0,0" VerticalAlignment="Top" Width="209"/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<string> _myList = new ObservableCollection<string>(new List<string>(){"1","2","3"});
int i = 3;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MyList.Add(i++.ToString());
}
public ObservableCollection<string> MyList
{
get { return _myList; }
set { _myList = value; }
}
}
}

How do I bind to a List<T> using DataContext?

I'm self-learning C#, OOP, and WPF so the potential for stuff ups is staggering.
So given that, can someone please explain why after clicking the button in my tiny test example the Name property appears in the TextBox but the ListBox shows nothing?
XAML:
<Window x:Class="BindingTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BindingTest" Height="250" Width="300">
<Grid Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Button
Grid.Row="0"
Name="MakeIntListButton"
Click="MakeIntListButton_Click">Make and Display Integer List</Button>
<TextBox Grid.Row="1" Text ="{Binding Path=Name}"
/>
<ListBox
Grid.Row="2"
ItemsSource="{Binding Path=MyIntegers}"
/>
</Grid>
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace BindingTest
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void MakeIntListButton_Click(object sender, RoutedEventArgs e)
{
AClass InstanceOfAClass = new AClass();
InstanceOfAClass.MyIntegers.Add(6);
InstanceOfAClass.MyIntegers.Add(7);
InstanceOfAClass.MyIntegers.Add(42);
InstanceOfAClass.Name = "Fred";
mainGrid.DataContext =InstanceOfAClass ;
}
}
public class AClass
{
public string Name {get;set;}
public List<int> MyIntegers = new List<int>();
}
}
Part of me wonders whether it's something to do with the fact that "MyIntegers" is a public field rather than a property. Can you refactor you class to look like this and try it?
public class AClass
{
private List<int> _ints = new List<int>();
public string Name { get; set; }
public List<int> MyIntegers
{
get { return _ints; }
}
}
I ran your sample and when I clicked on the button the TextBox was populated with the Name as expected.
The only problem I encountered was that the ListView was not getting populated with the list of integers. That's to do with the fact that XAML is not very comfortable with generics if you modify it to bind to an array instead it works. WPF supports consumption of XAML fine, it's using generics within XAML that's not supported. As Matt Hamilton points out in his answer MyIntegers just needs to be made a propety by adding a get acessor.
Add C# Property:
public int[] MyInts { get { return MyIntegers.ToArray(); } }
XAML:
<ListBox Grid.Row="2" ItemsSource="{Binding Path=MyInts}" />
Look into using the System.Collections.ObjectModel.ObservableCollection for list binding instead of a plain List.

Categories

Resources