my xml is :
<Window.Resources>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</Window.Resources>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="381*" />
<ColumnDefinition Width="20*" />
<ColumnDefinition Width="101*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="110*" />
<RowDefinition Height="201*" />
</Grid.RowDefinitions>
<StackPanel Margin="320,0,0,0" Grid.RowSpan="2">
<ListView ItemsSource="{Binding employeeCollection}">
<ListView.View>
<GridView>
<GridViewColumn Header="Employee ID" DisplayMemberBinding="{Binding Path=EmployeeID}"/>
<GridViewColumn Header="First Name" DisplayMemberBinding="{Binding Path=FirstName}"/>
<GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding Path=LastName}"/>
<GridViewColumn Header="start" DisplayMemberBinding="{Binding Path=startHR}"/>
<GridViewColumn Header="finish" DisplayMemberBinding="{Binding Path=finishHR}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
<StackPanel Margin="2,0,0,137" Grid.RowSpan="2" Grid.ColumnSpan="2" Grid.Column="1">
<ListBox FontFamily="Guttman Yad-Brush" BorderBrush="AliceBlue" BorderThickness="5" ItemsSource="{Binding Path=dateItems}" DisplayMemberPath="Name" SelectedValuePath="Name" SelectedValue="{Binding Path=dateItem}" Width="233" Height="164" />
</StackPanel>
<Button Click="Button_Click" Width="102" Height="34" Margin="0,98,-1,69" Grid.Row="1" Grid.Column="2" Content="בחר" FontFamily="Guttman Yad-Brush" Background="AliceBlue"></Button>
<TextBox Name="dateTextBox" Grid.Column="1" Margin="26,152,0,33" Grid.Row="1" FontFamily="Guttman Yad-Brush" Grid.ColumnSpan="2" />
<Calendar SelectedDate="{Binding Path=SelectedDate}" Height="168" Name="calendar1" Width="182" SelectedDatesChanged="calendar1_SelectedDatesChanged" Margin="66,68,485,115" Grid.RowSpan="2" />
</Grid>
-->
-->
this is the start window class :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Windows.Data;
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 WpfApplication1
{
public partial class MainWindow : Window
{
ConnectionViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ConnectionViewModel();
DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//((ConnectionViewModel)DataContext).dateItems = "test";
if (vm.cm.dateItem.ToString() != null)
{
dateTextBox.Text = vm.cm.dateItem.ToString();
vm.em.insert();
}
}
private void calendar1_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
string []s = calendar1.SelectedDate.ToString().Split(' ');
dateTextBox.Text = s[0];
}
}
public class ConnectionViewModel
{
public DateConectionModule cm;
public employeesGrid em;
public ConnectionViewModel()
{
cm = new DateConectionModule();
em = new employeesGrid();
}
public CollectionView dateItems
{
get { return cm.dateItems; }
}
public string dateItem
{
get {return cm.dateItem;}
set{ cm.dateItem = value;}
}
public CollectionView employeeCollection
{
get { return em.employeeCollection; }
}
}
public class DateConectionModule : INotifyPropertyChanged
{
public static string[] datesString = { "01.01.2011", "02.01.2011", "03.01.2011", "04.01.2011", "05.01.2011" };
public DateConectionModule()
{
employeesGrid em = new employeesGrid();
IList<dateItem> list = new List<dateItem>();
//query to database should be here
foreach (string dataString in datesString)
{
list.Add(new dateItem(dataString));
}
_dateItemList = new CollectionView(list);
}
private readonly CollectionView _dateItemList;
private string m_dateItem;
public CollectionView dateItems
{
get { return _dateItemList; }
}
public string dateItem
{
get { return m_dateItem; }
set
{
if (m_dateItem == value)
return;
m_dateItem = value;
OnPropertyChanged("dateItem");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class dateItem
{
public string Name { get; set; }
public dateItem(string name)
{
Name = name;
}
}
public class employeesGrid : INotifyPropertyChanged
{
private CollectionView _dateItemList;
private string m_dateItem;
public employeesGrid()
{
IList<employiesData> list = new List<employiesData>();
//query to database should be here
list.Add(new employiesData{
EmployeeID = "036854768",
FirstName = "yoav" ,
LastName = "stern",
startHR = "1600" ,
finishHR = "0200"});
_dateItemList = new CollectionView(list);
}
public void insert()
{
IList<employiesData> list = new List<employiesData>();
//query to database should be here
list.Add(new employiesData
{
EmployeeID = "0234235345",
FirstName = "shoki",
LastName = "zikri",
startHR = "1600",
finishHR = "0200"
});
_dateItemList = new CollectionView(list);
OnPropertyChanged("employeeCollection");
}
public CollectionView employeeCollection
{
get { return _dateItemList; }
set
{
if (_dateItemList == value)
return;
_dateItemList = value;
OnPropertyChanged("employeeCollection");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class employiesData
{
public string EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string startHR { get; set; }
public string finishHR { get; set; }
}
}
1.i want thar when insertTest is called the UI will load my new values
2.this is my first wpf work so any advices on how to make things more readable,effican,simple and notes about the my poor architecture i know it's crapy can some
Below my points
1- What is the use of ConnectionViewModel class , it is just warpping DataConnectionViewModel so i would suggest that you can get rid of ConnectionViewModel() and use DataConnectionViewModel.
2-i think you can Get rid of employeesGrid class because all you need a collection of employees so rather than useing a seprate collection class, make an observrablecollection in DataConnectionViewModel() class.
3- Use Wpf- Model -View-ViewModel Template this gives you better idea
4- i have just refacror your code and create a similar application which uses MVVM and ObservableCollection and much simpler to use.
my xaml
<Window.Resources>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</Window.Resources>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="381*" />
<ColumnDefinition Width="20*" />
<ColumnDefinition Width="101*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="110*" />
<RowDefinition Height="201*" />
</Grid.RowDefinitions>
<StackPanel Margin="320,0,0,0" Grid.RowSpan="2">
<ListView ItemsSource="{Binding EmpList}">
<ListView.View>
<GridView>
<GridViewColumn Header="Employee ID" DisplayMemberBinding="{Binding Path=EmployeeID}"/>
<GridViewColumn Header="First Name" DisplayMemberBinding="{Binding Path=FirstName}"/>
<GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding Path=LastName}"/>
<GridViewColumn Header="start" DisplayMemberBinding="{Binding Path=startHR}"/>
<GridViewColumn Header="finish" DisplayMemberBinding="{Binding Path=finishHR}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
<StackPanel Margin="2,0,0,137" Grid.RowSpan="2" Grid.ColumnSpan="2" Grid.Column="1">
<ListBox FontFamily="Guttman Yad-Brush" BorderBrush="AliceBlue" BorderThickness="5" ItemsSource="{Binding Path=dateItems}" DisplayMemberPath="Name" SelectedValuePath="Name" SelectedValue="{Binding Path=dateItem}" Width="233" Height="164" />
</StackPanel>
<!--<Button Click="Button_Click" Width="102" Height="34" Margin="0,98,-1,69" Grid.Row="1" Grid.Column="2" Content="בחר" FontFamily="Guttman Yad-Brush" Background="AliceBlue"></Button>-->
<TextBox Name="dateTextBox" Grid.Column="1" Margin="26,152,0,33" Grid.Row="1" FontFamily="Guttman Yad-Brush" Grid.ColumnSpan="2" />
<!--<Calendar SelectedDate="{Binding Path=SelectedDate}" Height="168" Name="calendar1" Width="182" SelectedDatesChanged="calendar1_SelectedDatesChanged" Margin="66,68,485,115" Grid.RowSpan="2" />-->
</Grid>
My Code
1-
Create DataConnectionViewModel which is inheriting ViewModelBase class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Employee.Models;
using System.Collections.ObjectModel;
namespace Employee.ViewModels
{
public class DateConectionModule : ViewModelBase
{
#region " Instance Variables "
public static string[] datesString = { "01.01.2011", "02.01.2011", "03.01.2011", "04.01.2011", "05.01.2011" };
#endregion " Instance Variables "
#region " Constructor "
public DateConectionModule()
{
CreateEmployeeData();
}
#endregion " Constructor "
#region " Public Properties "
public ObservableCollection<EmployeeData> EmpList { get; set; }
#endregion " Public Properties "
#region " Helper Methods "
private void CreateEmployeeData()
{
EmpList = new ObservableCollection<EmployeeData>();
EmpList.Add
(
new EmployeeData() { EmployeeID="1", LastName="Gates", FirstName="Bill", finishHR="", startHR ="" }
);
}
#endregion " Helper Methods "
}
}
2- ViewModelBAse Class
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
namespace Employee.ViewModels
{
///
/// Provides common functionality for ViewModel classes
///
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
3- Set the Data Context in the MainWindow.Xaml.cs
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
this.DataContext = new DateConectionModule();
}
}
there are lot of other things like Dependency Injection, etc.. but for your case , you can write a controller which call your dataservice to give you employess list than assign list to the observablecollection.
4- i can suggest read abot Prism framework which gives you very much flexibility during application management and also in TDD.
Related
I have a Button within a GridView within a ListView. When the button is clicked, I would like to get the text from the corresponding row of the Grid View for that button. Can anyone help me with the code for this?
My XAML is:
<ListView Name="resultList" ItemsSource="{Binding}" DockPanel.Dock="Top">
<ListView.View>
<GridView AllowsColumnReorder="true" ColumnHeaderToolTip="Test Results Summary">
<GridViewColumn DisplayMemberBinding= "{Binding Date}" Header="Date" Width="Auto"/>
<GridViewColumn DisplayMemberBinding= "{Binding Status}" Header="Status" Width="Auto"/>
<GridViewColumn Header="" Width="Auto">
<GridViewColumn.CellTemplate>
<DataTemplate >
<Button x:Name="btnRemoveTest" Click="_viewResult" Content="View" Height="20" Width="100"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
What can I do in here:
private void _viewResult(object sender, RoutedEventArgs e)
{
}
Use DataContext of Buttonas shown below. Please note that here in this example code, I'm using Customer.
private void _viewResult(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
Customer cust = button.DataContext as Customer;
string sDate = cust.Date;
string sStatus = cust.Status;
}
Below is the complete working code.
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
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;
namespace ListViewButtonClick
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
resultList.ItemsSource = new Customers();
}
private void _viewResult(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
Customer cust = button.DataContext as Customer;
string sDate = cust.Date;
string sStatus = cust.Status;
}
}
public class Customer
{
public String Date { get; set; }
public String Status { get; set; }
public Customer(String Date, String Status)
{
this.Date = Date;
this.Status = Status;
}
}
public class Customers : ObservableCollection<Customer>
{
public Customers()
{
Add(new Customer("11/23/2015", "in progress"));
Add(new Customer("11/24/2015", "Done"));
Add(new Customer("11/22/2015", "Not yet done"));
}
}
}
MainWindow.xaml
<Window x:Class="ListViewButtonClick.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>
<ListView Name="resultList" DockPanel.Dock="Top">
<ListView.View>
<GridView AllowsColumnReorder="true" ColumnHeaderToolTip="Test Results Summary">
<GridViewColumn DisplayMemberBinding= "{Binding Date}" Header="Date" Width="Auto"/>
<GridViewColumn DisplayMemberBinding= "{Binding Status}" Header="Status" Width="Auto"/>
<GridViewColumn Header="" Width="Auto">
<GridViewColumn.CellTemplate>
<DataTemplate >
<Button x:Name="btnRemoveTest" Click="_viewResult" Content="View" Height="20" Width="100"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
I have a window that uses a viewmodel. This screen contains 2 listviews on a screen. The first listview binds to a propery on my viewmodel called projects. This property returns a model as follows
class ProjectsModel
{
public string ProjectName { get; set; }
public ObservableCollection<ProjectModel> ProjectDetails { get; set; }
}
In this class the ProjectModel looks like the following
public class ProjectModel
{
public string ProjectName { get; set; }
public string ProjectId { get; set; }
public string ProjectFileId { get; set; }
public string ProjectSource { get; set; }
public string ClientCode { get; set; }
public string JobNumber { get; set; }
}
The first listview shows projectname as i expect but I would like it so that when I click on any of the items, the second listview should display its details of the projectdetails property. It almost appears to work has it shows the first items childrean but I beleive that its not being informed that the selected item of the first listview has changed. Ho can I do this? Any ideas would be appreciated becuase Ive been pulling my hair out for hours now!
This is the xaml
<Window x:Class="TranslationProjectBrowser.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TranslationProjectBrowser.ViewModels"
xmlns:local="clr-namespace:TranslationProjectBrowser.Models"
Title="MainWindow" Height="373" Width="452" Background="LightGray">
<Window.DataContext>
<vm:ProjectBrowserViewModel></vm:ProjectBrowserViewModel>
</Window.DataContext>
<Window.Resources>
<ObjectDataProvider x:Key="projectList" ObjectType="{x:Type vm:ProjectBrowserViewModel}" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource projectList}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="176*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="176*" />
<RowDefinition Height="254*" />
</Grid.RowDefinitions>
<DockPanel LastChildFill="True" >
<TextBlock DockPanel.Dock="Top" Text="Projects" Margin="5,2"></TextBlock>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBox Name="ProjectName" Width="140" Margin="5,2" Height="18" FontFamily="Calibri" FontSize="10"></TextBox>
<Button Height="18" Width="45" HorizontalAlignment="Left" Margin="0,2" FontSize="10" Content="Add" Command="{Binding Path=AddProject}" CommandParameter="{Binding ElementName=ProjectName, Path=Text}"></Button>
<TextBlock Text="{Binding Path=ErrorText}" VerticalAlignment="Center" Margin="6,2" Foreground="DarkRed"></TextBlock>
</StackPanel>
<ListView Name="project" HorizontalAlignment="Stretch" Margin="2" ItemsSource="{Binding Path=Projects}" IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=ProjectName}" Header="Name" Width="200" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
<DockPanel LastChildFill="True" Grid.Row="1" >
<TextBlock DockPanel.Dock="Top" Text="Project Files" Margin="5,2"></TextBlock>
<ListView HorizontalAlignment="Stretch" Margin="2" ItemsSource="{Binding Path=Projects/ProjectDetails}" IsSynchronizedWithCurrentItem="True" >
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=ProjectName}" Width="200" />
<GridViewColumn Header="Job Number" DisplayMemberBinding="{Binding Path=JobNumber}" Width="100" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Grid>
Your view models should (at least) implement INotifyPropertyChanged. This is how WPF will know when your selction (or other properties) change and the binding needs to be updated.
So you should have something like this:
class ProjectsModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public string ProjectName
{
get
{
return _projectName;
}
set
{
_projectName = value;
NotifyPropertyChanged("ProjectName");
}
}
public ObservableCollection<ProjectModel> ProjectDetails
{
get
{
return _projectDetails;
}
set
{
_projectDetails = value;
NotifyPropertyChanged("ProjectDetails");
}
}
}
In future versions of the .NET framework this gets a lot easier with the "caller info" attributes (http://www.thomaslevesque.com/2012/06/13/using-c-5-caller-info-attributes-when-targeting-earlier-versions-of-the-net-framework/). But as of today this is usually how it's done.
UPDATE
Ok, so based on your comment you need to bind your ListView's SelectedItem property to a property on your view model. You can then Bind your second ListView to that property as well. Something like this:
<ListView ... SelectedItem="{Binding Path=FirstListViewSelectedItem, Mode=TwoWay}" .. >
And then your second list view would be sometihng like this:
<ListView ... ItemsSource="{Binding Path=FirstListViewSelectedItem.ProjectDetails, Mode=OneWay" .. />
I don't see any current management in your code. If you use a CollectionView you will get that for free, see below sample:
XAML:
<Window x:Class="WpfApplication1.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">
<StackPanel>
<ListBox ItemsSource="{Binding Path=ProjectsView}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True"/>
<ListBox ItemsSource="{Binding Path=ProjectsView/ProjectDetails}" />
</StackPanel>
</Window>
Code behind:
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new VM();
}
}
public class VM
{
public VM()
{
List<Project> projectsModel = new List<Project>();
projectsModel.Add(new Project("AAA"));
projectsModel.Add(new Project("BBB"));
projectsModel.Add(new Project("CCC"));
ProjectsView = CollectionViewSource.GetDefaultView(projectsModel);
}
public ICollectionView ProjectsView { get; private set; }
}
public class Project
{
public Project(string name)
{
Name = name;
}
public string Name { get; private set; }
public IEnumerable<string> ProjectDetails
{
get
{
for (int i = 0; i < 3; i++)
{
yield return string.Format("{0}{1}", Name, i);
}
}
}
}
}
Is there any way to update listbox items text after updating it in textboxes? I wanna do it only with bindings if it is possible. Listbox is reading from list, but list isn't updating so it's never gonna change, unless I add new item to list. Here is code
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DockPanel VerticalAlignment="Top" HorizontalAlignment="Left">
<Button Name="dodaj" Width="40" Margin="5" Click="dodaj_Click">Dodaj</Button>
<Button Name="usun" Width="40" Margin=" 5" Click="usun_Click">Usuń</Button>
</DockPanel>
<DockPanel HorizontalAlignment="Left" VerticalAlignment="Center" DataContext="{Binding ElementName=lista, Path=osoba, Mode=TwoWay}">
<ListBox Name="lb" ItemsSource="{Binding}" Width="200" Height="230">
</ListBox>
</DockPanel>
<DockPanel HorizontalAlignment="Right" VerticalAlignment="top">
<WrapPanel>
<StackPanel>
<Label>Imię</Label>
<Label>Nazwisko</Label>
<Label>Email</Label>
<Label>Telefon</Label>
</StackPanel>
<StackPanel DataContext="{Binding ElementName=lb, Path=SelectedItem, UpdateSourceTrigger=LostFocus}" TextBoxBase.TextChanged="zmiana">
<TextBox Width="200" Margin="3" Name="imie" Text="{Binding Path=imie}"></TextBox>
<TextBox Width="200" Name="nazwisko" Text="{Binding Path=nazwisko}"></TextBox>
<TextBox Width="200" Margin="3" Name="email" Text="{Binding Path=email}"></TextBox>
<TextBox Width="200" Name="telefon" Text="{Binding Path=telefon}"></TextBox>
</StackPanel>
</WrapPanel>
</DockPanel>
</Grid>
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;
using System.ComponentModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lista.Add(new dane("imie1", "nazwisko1", "email1", "tel1"));
lista.Add(new dane("imie2", "nazwisko2", "email2", "tel2"));
lb.DataContext = lista;
}
public class dane : INotifyPropertyChanged
{
public dane(string imie, string nazwisko, string email, string telefon)
{
this._imie = imie;
this.nazwisko = nazwisko;
this.osoba = nazwisko + " " + imie;
this.email = email;
this.telefon = telefon;
}
private string _imie;
public string imie
{
set
{
_imie = value;
OnPropertyChanged("Imie");
}
get
{
return _imie;
}
}
public string nazwisko { set; get; }
public string osoba { set; get; }
public string email { set; get; }
public string telefon { set; get; }
public override string ToString()
{
return osoba;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string value)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(value));
}
}
}
public ObservableCollection<dane> lista = new ObservableCollection<dane>();
//public List<dane> lista = new List<dane>();
/*public ObservableCollection<dane> lista
{
get { return (ObservableCollection<dane>)GetValue(listaProperty); }
set { SetValue(listaProperty, value); }
}
public static readonly DependencyProperty listaProperty =
DependencyProperty.Register("lista", typeof(ObservableCollection<dane>), typeof(Window), new UIPropertyMetadata(null));
*/
private void dodaj_Click(object sender, RoutedEventArgs e)
{
lista.Add(new dane("...", "", "", ""));
MessageBox.Show(lista[0].ToString());
}
private void usun_Click(object sender, RoutedEventArgs e)
{
lista.RemoveAt(lb.SelectedIndex);
}
}
}
Listbox shows instances of your dane class by their ToString() representation. You should bind listbox to osoba property directly with DisplayMemberPath. And also value of osoba property is not udated then you change imia and nazwisko. You should recalculate it on its getter and also raise PropertChanged("osoba") then properties nazwisko or imia are being changed.
Bellow is the code behind and the Xaml for a demo app to review databing and wpf.
The problem is binding Store.ImagePath property to the person node is not working. That is the image is not showing.
<Image Source="{Binding Path=Store.ImagePath, RelativeSource={RelativeSource AncestorType={x:Type local:Store}}}" />
Here is the code-behind
namespace TreeViewDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Customers customers = new Customers();
customers.Users = new List<Person>
{
new Person { Name = "John"},
new Person { Name = "Adam"},
new Person { Name = "Smith"}
};
Store store = new Store();
store.AllCustomers.Add(customers);
this.DataContext = store;
}
}
public class Store : INotifyPropertyChanged
{
string imagePath = "imageone.png";
public Store()
{
AllCustomers = new ObservableCollection<Customers>();
}
public string StoreName
{
get
{
return "ABC Store";
}
}
public ObservableCollection<Customers> AllCustomers
{
get;
set;
}
public string ImagePath
{
get
{
return imagePath;
}
set
{
if (value == imagePath) return;
imagePath = value;
this.OnPropertyChanged("ImagePath");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Customers
{
public string Label
{
get
{
return string.Format("People({0})", Users.Count());
}
}
public List<Person> Users
{
get;
set;
}
}
public class Person : INotifyPropertyChanged
{
public string Name
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and here is the Xaml.
<Window x:Class="TreeViewDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewDemo"
Title="MainWindow" Height="350" Width="525">
<Window.Resources >
<DataTemplate DataType="{x:Type local:Person}" x:Key="personKey" >
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Path=Store.ImagePath, RelativeSource={RelativeSource AncestorType={x:Type local:Store}}}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="customerKey" ItemsSource="{Binding Users}" ItemTemplate="{StaticResource personKey }" >
<TextBlock Text="{Binding Label}" FontWeight="Bold"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<Canvas>
<Button HorizontalAlignment="Left" DockPanel.Dock="Top" Height="29" Width="112" Canvas.Left="123" Canvas.Top="5">Image one</Button> <Button HorizontalAlignment="Left" VerticalAlignment="Top" DockPanel.Dock="Top" Height="28" Width="119" Canvas.Left="249" Canvas.Top="7">Image two</Button>
<TreeView HorizontalAlignment="Stretch" Name="treeView1" VerticalAlignment="Stretch"
ItemsSource="{Binding .}" Height="260" Width="363" Canvas.Left="81" Canvas.Top="45">
<TreeViewItem ItemsSource="{Binding AllCustomers}" ItemTemplate="{StaticResource customerKey}" Header="{Binding StoreName}"></TreeViewItem>
</TreeView>
</Canvas>
</Grid>
</Window>
All files are in the same directory.
Thanks
A relative source is used to look up an object in the visual tree. You're asking it to find the nearest Store in the visual tree. Since a Store cannot even be in the visual tree, the lookup will fail and yield null. What you actually want is the DataContext of the root Window, since that is where your Store is held:
<Image Source="{Binding DataContext.ImagePath, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
I have successfully applied the trick explained here. But I still have one problem.
Quick recap : I display users in a ListView. Users are regrouped by Country, and in the GroupStyle DataTemplate I display the sum of all group related Users.Total, using a Converter. But UI users can change the "Total" property value of Users through a modal window.
When there is only one item in the Group, both the User Total displayed and the sum are properly updated. But when there are multiple items in the group, only the User Total is updated (through binding) but the Converter that's supposed to make the sum (TotalSumConverter) is not even called!
Do you have any idea where it could come from? Should I use some kind of a trigger to make sure the Converter is called when there is a modification in the items?
The problem is that the value converter that calculates the sum for all the items in a group don't run when an item is changed, since there's no notification for changed items. One solution is to bind to something else that you can control how it does notifications and notify the group header when needed.
Below is a working example. You can change the count for a user in the text box and totals gets recalculated.
XAML:
<Window x:Class="UserTotalTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:userTotalTest="clr-namespace:UserTotalTest"
Title="Window1" Height="300" Width="300"
Name="this">
<Window.Resources>
<userTotalTest:SumConverter x:Key="SumConverter" />
<CollectionViewSource Source="{Binding Path=Users}" x:Key="cvs">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Country"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListView
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
ItemsSource="{Binding Source={StaticResource cvs}}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}" />
<GridViewColumn Header="Count" DisplayMemberBinding="{Binding Path=Count}" />
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel Margin="10">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
<ItemsPresenter />
<TextBlock FontWeight="Bold">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource SumConverter}">
<MultiBinding.Bindings>
<Binding Path="DataContext.Users" ElementName="this" />
<Binding Path="Name" />
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<ComboBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
ItemsSource="{Binding Path=Users}"
DisplayMemberPath="Name"
SelectedItem="{Binding Path=SelectedUser}" />
<TextBlock Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding Path=SelectedUser.Country}" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding Path=SelectedUser.Count}" />
</Grid>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace UserTotalTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new UsersVM();
}
}
public class UsersVM : INotifyPropertyChanged
{
public UsersVM()
{
Users = new List<User>();
Countries = new string[] { "Sweden", "Norway", "Denmark" };
Random random = new Random();
for (int i = 0; i < 25; i++)
{
Users.Add(new User(string.Format("User{0}", i), Countries[random.Next(3)], random.Next(1000)));
}
foreach (User user in Users)
{
user.PropertyChanged += OnUserPropertyChanged;
}
SelectedUser = Users.First();
}
private void OnUserPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Count")
{
PropertyChanged(this, new PropertyChangedEventArgs("Users"));
}
}
public List<User> Users { get; private set; }
private User _selectedUser;
public User SelectedUser
{
get { return _selectedUser; }
set
{
_selectedUser = value; if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedUser"));
}
}
}
public string[] Countries { get; private set; }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class User : INotifyPropertyChanged
{
public User(string name, string country, double total)
{
Name = name;
Country = country;
Count = total;
}
public string Name { get; private set; }
private string _country;
public string Country
{
get { return _country; }
set
{
_country = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Country"));
}
}
}
private double _count;
public double Count
{
get { return _count; }
set
{
_count = value; if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class SumConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
IEnumerable<User> users = values[0] as IEnumerable<User>;
string country = values[1] as string;
double sum = users.Cast<User>().Where(u =>u.Country == country).Sum(u => u.Count);
return "Count: " + sum;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
The trick you are using databinds the group footer to ListView.Items which will not update your view automatically, like a DependencyObject does for example. Instead force a refresh after each update to Total like this:
CollectionViewSource viewSource = FindResource("ViewSource") as CollectionViewSource;
viewSource.View.Refresh();
The problem with refreshing the view is that it completely refreshes it, and I have expanders for my grouping, that will expand back to their original state, even if the user closed them. So yes it's a possible workaround, but it's not completely satisfying.
Also your DependencyObject explanation is interesting, but then, why is it working when I have only one item in my group?