Listview TwoWay Binding With ObservableCollection - wpf

:::::::::: ObservableCollection ::::::::::
ObservableCollection<Friend> Friends = new ObservableCollection<Friend> ( );
:::::::::: InitializeFriends ::::::::::
private void InitializeFriends ( )
{
JsonObject _JsonObject = Process . FacebookClient . Get ( "/me/friends" ) as JsonObject;
if ( _JsonObject != null )
{
JsonArray _JsonArray = _JsonObject [ "data" ] as JsonArray;
foreach ( JsonObject Friend in _JsonArray )
{
Friends . Add ( new Friend ( ) { Name = Friend [ "name" ] . ToString ( ) } );
}
}
}
:::::::::: Friend ::::::::::
public class Friend
{
public string Name { get; set; }
public Conversation Chat { get; set; }
public BitmapImage Image { get; set; }
}
:::::::::: ListView ::::::::::
<ListView Name="Panel"
Width="Auto"
Margin="0,200,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
ItemsSource="{Binding Path=Friends,
Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionMode="Single">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource hiddenStyle}">
<GridViewColumn Width="200" DisplayMemberBinding="{Binding Path=Name}" />
<GridViewColumn Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<Image Width="30"
Height="30"
Source="{Binding Path=Image}" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
List View Not Binding May Be Binding But Not Updating ?!!!
There is something WRONG

Your Friend class needs to implement the INotifyPropertyChanged interface, so that WPF notifies when a change has been made to that property.

When you have a problem with XAML bindings the first thing to do is run up your program under visual studio and look in your output window for any errors or warnings that mention XAML or Bindings. If there is a problem it will likely show up there.
Your output window can be opened by selecting View->Output from the main menu (or typing Ctrl+W, O). If you're not seeing anything then select Debug in the "Show Output From" combobox.
If you're still not seeing anything then you might need to change your WPF trace level. You can do this by selecting Debug from the main menu and choose “Options and Settings…”. Expand the Debugging node and select “Output Window”. Now in the right hand pane set the “Data Binding” value in "WPF Trace Settings" to at least Warning. You should see some info in the output window next time you run the app under VS.
If you’re still not getting enough information you can alter the trace level (in .Net 3.5 and above) in XAML by adding the System.Diagnostics namespace to your XAML file:
xmlns:diagnostics=”clr-namespace:System.Diagnostics;assembly=WindowsBase”
and setting the trace level in the binding of interest like this:
<ListView Name="Panel"
Width="Auto"
Margin="0,200,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
ItemsSource="{Binding Path=Friends,
Mode=TwoWay,
diagnostics:PresentationTraceSources.TraceLevel=High}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionMode="Single">

Your Friends collection needs to be exposed as a public property, i.e.:
private readonly ObservableCollection<Friend> _friends = new ObservableCollection<Friend>();
public ObservableCollection<Friend> Friends { get { return _friends; } }

if your datacontext is right your binding should work. btw you just need Mode=OneWay
<ListView Name="Panel" ItemsSource="{Binding Path=Friends, Mode=OneWay}" />
so you should first check your DataContext. the easiest way is to use Snoop. you can also check your visual studio output window to see binding errors

Please add this property to the XAML Window:
DataContext="{Binding RelativeSource={RelativeSource Self}}">

Related

ListView items do not display

I have a rather simple ListView with GridView style. Cells are binded to some properties of an ObservableCollection elements. I have no idea why thaey are not displaying. I were thinking about implementing INotifyPropertyChanged in a class of element contained by collection, but once an element is constructed and added to a collection it is never changed so it should be working just fine.
C#:
public ObservableCollection<SceneElement> SceneObjects { get; set; }
...
SceneObjects.Add(new Camera());
SceneObjects.Add(new Cuboid(20, 30, 40));
Camera and Cuboid both inherit from SceneObject (SceneObject <- Figure <- Cuboid and SceneObject <- Camera)
XAML:
<ListView Grid.Column="2" Margin="5"
ItemsSource="{Binding }"
DataContext="{Binding SceneObjects}"
ScrollViewer.CanContentScroll="True">
<ListView.Resources>
<GridView x:Key="StyleOne">
<GridViewColumn Header="Type" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="1" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Position" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="X: " />
<TextBlock Text="{Binding Position.X, RelativeSource={RelativeSource Mode=FindAncestor}}" />
<TextBlock Text=" Y: " />
<TextBlock Text="{Binding Position.Y}" />
<TextBlock Text=" Z: " />
<TextBlock Text="{Binding Position.Z}" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.Resources>
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="View" Value="{StaticResource StyleOne}" />
</Style>
</ListView.Style>
</ListView>
Position property is defined inside an abstract SceneElement class.
Position's X, Y and Z are properties as well.
EDIT
This has solved the issue:
private ObservableCollection<SceneElement> sceneObjects;
public ObservableCollection<SceneElement> SceneObjects
{
get
{
return sceneObjects;
}
set
{
sceneObjects = value;
NotifyPropertyChanged("SceneObjects");
}
}
Can anyone explain why this was necessary? I've used ObservableCollection severeal time so far and it always worked without notifying.
Reason why your application wasn't displaying the information about the collection is because the UI wasn't aware of any changes.
Using ObservableCollection doesn't guarantee the results will be displayed if said collection is not initialized before it is used. As it is a plain property on the ViewModel it needs to notify if said object, in this case ObservableCollection has been changed, but not in the sense items inside of the collection.
This is due to the fact of NOT raising the PropertyChanged event for SceneObjects, which is crucial for WPF apps using Bidning.
at the point where the wpf system attempts to create the binding, your observablecollection was null, as xamimax above said.
instantiate your observablecollection in your constructor, and later you can add the items and they will appear.
for example, the following works because i first instantiated the observable collection and then added items 5 seconds later.
public MainWindow()
{
InitializeComponent();
this.SceneObjects = new ObservableCollection<MyClass>();
Task.Delay(5000).ContinueWith((old) =>
{
this.Dispatcher.Invoke(() =>
{
this.sceneObjects.Add(new MyClass("name"));
this.sceneObjects.Add(new MyClass("name"));
});
});
this.DataContext = this;
}
and here is an example that doesnt work, because i instantiate the observable collection 5 secs after the wpf system tried to bind to it:
public MainWindow()
{
InitializeComponent();
Task.Delay(5000).ContinueWith((old) => { this.SceneObjects = new ObservableCollection<MyClass> { new MyClass("name"), new MyClass("name") }; });
this.DataContext = this;
}
and as you already realized, you can throw propchanged to make wpf system display those items, but i do think you should simply instantiate the observablecollection earlier so that you dont need to throw propchanged later.

Setting DataContext for binding RelayCommand in xaml

In the following xaml code, I'm trying bind a RelayCommand ResourceButtonClick, which is in the view model. In addition to that, I want to pass the Resource.Id as a parameter to this command.
However, ResourceButtonClick is not called. I suspect that by setting the ItemsSource to Resources, I override the data context, which was view model.
<UserControl ...>
<Grid>
<ItemsControl ItemsSource="{Binding Resources}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Tag="{Binding Id}" Content="{Binding Content}"
Width="300" Height="50"
Command="{Binding ResourceButtonClick}"
CommandParameter="{Binding Id}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Here's the RelayCommand in the view model.
public RelayCommand<int> ResourceButtonClick { get; private set; }
The constructor of the view model:
public ResourcesViewModel()
{
this.ResourceButtonClick =
new RelayCommand<int>((e) => this.OnResourceButtonClick(e));
}
The method in the view model:
private void OnResourceButtonClick(int suggestionId)
{
...
}
I have two questions: First, how can I call the ResourceButtonClick command. Second, how can I pass Resource.Id as a parameter to that command.
Any suggestion will be appreciated.
Maybe you can show your complete ViewModel class? Assuming, that the ResourceButtonClick command is on the ViewModel which also holds the collection Resources, you're trying to access the command on the wrong object (on the item in Resources, instead of the ViewModel which holds the Resources collection and the command).
Therefore you would have to access the command on the 'other' DataContext, this is the DataContext of the ItemsControl not of it's item. The easiest way is to use the ElementName attribute of the binding:
<UserControl ...>
<Grid>
<ItemsControl ItemsSource="{Binding Resources}" Name="ResourcesItemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Tag="{Binding Id}" Content="{Binding Content}"
Width="300" Height="50"
Command="{Binding DataContext.ResourceButtonClick, ElementName=ResourcesItemsControl}"
CommandParameter="{Binding Id}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Maybe this solves the problem, otherwise please let me know and provide some more detail.
I usually pass the whole item as a CommandParameter, not an ID. This doesn't cost anything and you don't have to translate the ID back to the item. But that depends on your case.
Hope this helps.
I resolved this in a such way:
In View.xaml
1) I added a property SelectedItem for my ListView:
<ListView Name="MyList" ItemsSource="{Binding MyList}" SelectedItem="{Binding MySelectedItem, Mode=TwoWay}" >
2) I added a Command property to a button:
In viewModel:
3) I added a command handler:
MyCommand = new RelayCommand(MyMethod);
4) I added method MyMethod which takes value(s) from MySelectedItem property:
private void MyMethod ()
{
MyType mt = MySelectedItem;
//now you have access to all properties of your item via mt object
}

Switch between different views, following MVVM pattern

I'm looking for the correct way to switch between different views. My scenario is similar to the Widnows Explorer on Windows 8, where you can switch between 'extra large icons', 'small icons', 'details', etc. In Windows 8 The users do that from the 'View' ribbon of explorer (you can also change view in XP and 7).
In my case, I've a list of friends, and I want to let the user switch between 'small', 'large', and 'details' view.
Assume my view model has a list of friends:
public class FriendVM {
public name { get; set; }
public smallImage { get; set; }
public largeImage { get; set; }
};
public class MainVM : INotifyPropertyChanged {
public ObservableCollection<FriendVM> friends { get; set; }
private string m_viewMode
public string viewMode {
get { return m_viewMode; }
set { m_viewMode=value; this.PropertyChanged( new PropertyChangedEventArags("viewMode") ); }
}
}
In my view, I've a ribbon (on which user can change viewMode), a header (showing details about the user), and a list of friends. Depends on the view, I want to show:
when viewMode = "details" I've a ListView, with a GridView.
when viewMode = "small" I've a ListView, with ItemsPanel is a WrapPanel, and I bind the image to the smallImage
when viewMode = "large" I've a ListView with WrapPanel, using the largeImage property.
Here is how my XML looks like (simplified):
<Window x:Class="friends.MainWindow" ... xmlns:f="clr-namespace:friends" ...>
<Window.DataContext>
<f:MainVM />
<Window.DataContext>
<Window.Resources>
<ControlTemplate x:Key="details">
<ListView ItemsSource="{Binding Path=friends}">
<ListView.View>
<GridView>
...
</GridView>
</ListView.View>
</ControlTemplate>
<ControlTemplate x:Key="small">
<ListView ItemsSource="{Binding Path=friends}">
<ListView.ItemsPanel><WrapPanel Orientation="Horizontal" />
</ListView>
<ListView.ItemTemplate><DataTemplate>
<Image Source={Binding smallPicture} Width="32" Height="32" />
</DataTemplate></ListView.ItemTemplate>
</ControlTemplate>
<ControlTemplate x:Key="large">
<ListView ItemsSource="{Binding Path=friends}">
<ListView.ItemsPanel><WrapPanel Orientation="Horizontal" />
</ListView>
<ListView.ItemTemplate><DataTemplate>
<StackPanel>
<Image Source="{Binding largePicture}" Width="200" Height="200" />
<TextBlock Text="{Binding name}" />
</StackPanel>
</DataTemplate></ListView.ItemTemplate>
</ControlTemplate>
</Window.Resource>
<DockPanel LastChildFill="True">
<Ribbon ...>
...
</Ribbon>
<StackPanel>
... some header stuff
</StackPanel>
<ContentControl x:Name="friendList" Content="{Binding friends}" ?????? />
</DockPanel>
</Window>
So, my question is what do I do in the ????? area. Right now, I've Template="{StaticResource small}" and it is working. Moreover, using code behind I can change the Template to any of the other resourced template (using FindResource). However, I'm not happy with this solution, as I feel it doesn't go well with MVVM pattern. If it was an "Item" (a listbox item, a tab item, etc.), then I could use a data template, and then a date template selector. But since this is a ContentControl, and ControlTemplateSelector seems to be completely broken from the design, I'm not sure what should I do.
Or, if I could put the list of friends "as is" in the tree, then maybe using data template (having TargetType=f:FriendList) I could make it work, but I don't want to instantiate another friend list. There is already one instance instantiated within the DataContext element.

WPF ListView SelectedValue not being set

I've looked at a few solutions but nothing has worked yet for me.
I'm using MVVM for this project and have a ListView that I can't set the SelectedItem property.
If this is my (simplified) XAML.
<ListView Name="uxPackageGroups" ItemsSource="{Binding Path=PackageGroups, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" BorderThickness="0"
BorderBrush="#FF0000E8" ScrollViewer.CanContentScroll="True"
SelectedItem="{Binding Path=PackageGroupSelectedItem, Mode=TwoWay}" >
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}" Height="20" Margin="0" Padding="0"/>
</DataTemplate>
</ListView.ItemTemplate>
And I bind it to a PackageGroups in my ViewModel
public PackageGroup PackageGroupSelectedItem {get; set; }
public ObservableCollection<PackageGroup> PackageGroups {get; set; }
private void LoadUI()
{
PackageGroups = Factory.LoadAllPackageGroups())
// if I try to hard-code a pre-selected item here it doesn't work.
// 34 is a valid ID and I see a valid object when stepping through the code
PackageGroupSelectedItem = PackageGroup.LoadByID(db, 34);
}
Anything glaring in my code?
Thanks.
One possible problem is that you're not implementing INotifyPropertyChanged in the PackageGroupSelectedItem property.
I've just came into the same situation and it turned out that my collection item had incorrectly implemented "Equals" method. It doesn't need to implement INotifyPropertyChanged on collection item, but Equals should be implemented correctly...

Listview of items from a object selected in another listview

Ok the title maybe a little confusing. I have a database with the table Companies wich has one-to-many relotionship with another table Divisions ( so each company can have many divisions ) and division will have many employees.
I have a ListView of the companies. What I wan't is that when I choose a company from the ListView another ListView of divisions within that company appears below it. Then I pick a division and another listview of employees within that division appaers below that. You get the picture.
Is there anyway to do this mostly inside the XAML code declaritively (sp?). I'm using linq so the Company entity objects have a property named Division wich if I understand linq correctly should include Division objects of the divisions connected to the company. So after getting all the companies and putting them as a itemsource to CompanyListView this is where I currently am.
<ListView x:Name="CompanyListView"
DisplayMemberPath="CompanyName"
Grid.Row="0" Grid.Column="0" />
<ListView DataContext="{Binding ElementName=CompanyListView, Path=SelectedItem}"
DisplayMemberPath="Division.DivisionName"
Grid.Row="1" Grid.Column="0" />
I know I'm way off but I was hoping by putting something specific in the DataContext and DisplayMemberPath I could get this to work. If not then I have to capture the Id of the company I guess and capture a select event or something.
Another issue but related is the in the seconde column besides the lisview I wan't to have a details/edit view for the selected item. So when only a company is selected details about that will appear then when a division under the company is picked It will go there instead, any ideas?
You can use the MVVM pattern to bind your XAML to a class that contains information for your ListViews and reset the content for the Division collection based on the selected Comany item.
Here is a basic sample to get you started.
Here are two ListView controls in XAML:
<Window x:Class="MultiListView.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Main Window" Height="400" Width="800">
<DockPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemsSource="{Binding Companies}"
SelectedItem="{Binding Company, Mode=TwoWay}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding CompanyName}" />
<GridViewColumn Header="Description"
DisplayMemberBinding="{Binding Description}" />
</GridView>
</ListView.View>
</ListView>
<ListView Grid.Row="1"
ItemsSource="{Binding Divisions}"
SelectedItem="{Binding Division, Mode=TwoWay}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding DivisionName}" />
<GridViewColumn Header="Description"
DisplayMemberBinding="{Binding Description}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</DockPanel>
</Window>
In the code-behind set the DataContext for the Window to a class the contains the binding references used in the XAML.
public partial class MainView : Window
{
MainViewModel _mvm = new MainViewModel();
public MainView()
{
InitializeComponent();
DataContext = _mvm;
}
}
The following class uses the MVVM pattern, which you can find lots of information
on in StackOverFlow. This class contains the data that the XAML binds with. Here
is where you can use LINQ to load/reload the collections.
using System.Collections.ObjectModel;
using MultiListView.Models;
namespace MultiListView.ViewModels
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
_companies = new ObservableCollection<Company>();
_companies.Add(new Company("Stackoverflow", "QA web site"));
_companies.Add(new Company("Fog Creek", "Agile Bug Tracking"));
_companies.Add(new Company("Second Beach", "Not sure yet"));
_divisions = new ObservableCollection<Division>();
}
private ObservableCollection<Company> _companies;
public ObservableCollection<Company> Companies
{
get { return _companies; }
set
{
_companies = value;
OnPropertyChanged("Companies");
}
}
private Company _company;
public Company Company
{
get { return _company; }
set
{
_company = value;
// load/reload divisions for the selected company here
LoadDivisions();
OnPropertyChanged("Company");
}
}
// hack to keep the example simpe...
private void LoadDivisions()
{
_divisions.Clear();
// use db or linq here to filiter property
if ( _company != null )
{
if ( _company.CompanyName.Equals("Stackoverflow") )
{
_divisions.Add( new Division("QA", "Test all day"));
_divisions.Add( new Division("Write", "Doc all day"));
_divisions.Add( new Division("Code", "Code all day"));
}
else if (_company.CompanyName.Equals("Fog Creek"))
{
_divisions.Add(new Division("Test", "Test all day"));
_divisions.Add(new Division("Doc", "Doc all day"));
_divisions.Add(new Division("Develop", "Code all day"));
}
else if (_company.CompanyName.Equals("Second Beach"))
{
_divisions.Add(new Division("Engineering", "Code all day"));
}
}
}
private ObservableCollection<Division> _divisions;
public ObservableCollection<Division> Divisions
{
get { return _divisions; }
set
{
_divisions = value;
OnPropertyChanged("Divisions");
}
}
private Division _division;
public Division Division
{
get { return _division; }
set
{
_division = value;
OnPropertyChanged("Division");
}
}
}
}
OnPropertyChanged implements INotifyPropertyChanged.
When the properties of a ViewModel change, the Views bound to the ViewModel receive a notification when the ViewModel raises its PropertyChanged event.
You can find examples in most MVVM libraries, or look to MSDN for an example.
If Divisions is a property of Company, you could probably do something like this:
<Window x:Class="MultiListView.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Main Window" Height="400" Width="800">
<DockPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView Grid.Row="0"
x:Name="lvCompanies"
ItemsSource="{Binding Companies}"
SelectedItem="{Binding Company, Mode=TwoWay}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding CompanyName}" />
<GridViewColumn Header="Description"
DisplayMemberBinding="{Binding Description}" />
</GridView>
</ListView.View>
</ListView>
<ListView Grid.Row="1"
ItemsSource="{Binding ElementName='lvCompanies', Path=SelectedItem.Divisions}"
SelectedItem="{Binding Division, Mode=TwoWay}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding DivisionName}" />
<GridViewColumn Header="Description"
DisplayMemberBinding="{Binding Description}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</DockPanel>
</Window>

Resources