Here is my ViewModel Class
namespace ERP_Lite_Trial.ViewModels
{
public class GroupsViewModel : INotifyPropertyChanged
{
public GroupsViewModel()
{
using (DBEntities db = new DBEntities())
{
var groups = (from g in db.Groups
select g.Name).ToList();
this.GroupName = groups;
var correspondingEffects = (from g in db.Groups
select g.Type_Effect.Name).ToList();
this.EffectCorrespondingToGroup = correspondingEffects;
}
}
private List<string> _groupName;
public List<string> GroupName
{
get
{
return _groupName;
}
set
{
_groupName = value;
OnPropertyChanged("GroupName");
}
}
private List<string> _effectCorrespondingToGroup;
public List<string> EffectCorrespondingToGroup
{
get
{
return _effectCorrespondingToGroup;
}
set
{
_effectCorrespondingToGroup = value;
OnPropertyChanged("EffectCorrespondingToGroup");
}
}
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Now I will show you the two cases :
Case 1 : Works Well
<ComboBox x:Name="cbUnder" ItemsSource="{Binding Path=GroupName}" IsEditable="True"
Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3" />
In the above case I get all the group names from my database and are displayed correctly as items of the comboBox. But this is not what I want. I want to display two columns in this combobox.
Case 2: Not working as expected (There might be some silly mistake done by me)
<ComboBox x:Name="cbUnder" IsEditable="True" Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=GroupName}" Width="100"/>
<TextBlock Text="|" Width="10" />
<TextBlock Text="{Binding Path=EffectCorrespondingToGroup}" Width="100"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I did not get any errors in this case but my combobox is not showing any Items.
Your code needs to create the information you currently have in two lists as they relate to each other in one list. As two separate lists, there is no way to relate them to one another.
First change your DB query to return the information as a list of objects.
using (DBEntities db = new DBEntities())
{
GroupsAndEffects = (from g in db.Groups
select new GroupAndEffect
{
GroupName = g.Name
EffectCorrespondingToGroup = g.Type_Effect.Name
}).ToList();
}
the var groups needs to be a list of objects, instead of a list of strings:
private List<GroupAndEffect> _groupAndEffects;
public List<GroupAndEffect> GroupsAndEffects
{
get
{
return _groupAndEffects;
}
set
{
_groupAndEffects = value;
OnPropertyChanged("GroupsAndEffects");
}
}
Requiring a GroupAndEffect Class
public class GroupAndEffect
{
public string GroupName;
public string EffectCorrespondingToGroup;
}
Update Case 2:
<ComboBox x:Name="cbUnder" ItemsSource="{Binding GroupsAndEffects}" IsEditable="True" Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}"/>
<TextBlock Text="|" Width="10" />
<TextBlock Text="{Binding EffectCorrespondingToGroup}" Grid.Column="1" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate></ComboBox>
Related
I am working with data binding and tree views and I am not able to get my TreeView to populate in my WPF. I think I am relatively close, just a small tweak somewhere, but I can't seem to find it.
Here's my Project class:
public class Project
{
public Project(string Name, bool isFolder, Project ParentFolder)
{
this.Name = Name;
this.isFolder = isFolder;
Children = new List<Project>();
if (ParentFolder == null)
{
Path = Name;
}
else
{
Path = ParentFolder.Path + " > " + Name;
}
}
public string Path { get; private set; }
public string Name { get; set; }
public bool isFolder { get; set; }
public List<Project> Children { get; set; }
public IEnumerable<Project> ChildFolders
{
get
{
return Children.Where(p => p.isFolder);
}
}
public object Icon
{
get
{
if (isFolder)
{
return 0; // return folder icon
}
else
{
return 1; // return project icon
}
}
}
public IEnumerable<Project> SearchRecursively(string SearchString)
{
return GetAllChildren.Where(p => p.Name.Contains(SearchString));
}
private List<Project> GetAllChildren
{
get
{
List<Project> allChildren = new List<Project>();
foreach(Project child in Children)
{
allChildren.AddRange(child.GetAllChildren);
}
return allChildren;
}
}
}
}
Here is my MaiWindow.xaml.cs class that I will be using to make test data:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.BuildData();
}
private void BuildData()
{
List<Project> parents = new List<Project>();
Project parentOne = new Project("Apple", true, null);
Project parentTwo = new Project("Samsung", true, null);
Project parentThree = new Project("Google", true, null);
parents.Add(parentOne); parents.Add(parentTwo); parents.Add(parentThree);
Project appleMacBook = new Project("Mac", false, parentOne);
Project appleIpad = new Project("iPad", false, parentOne);
Project appleiPhone = new Project("iPhone", false, parentOne);
Project samsungGalaxy = new Project("Galaxy", false, parentTwo);
Project samsungNote = new Project("Note", false, parentTwo);
Project googlePixel = new Project("Pixel", false, parentThree);
Project googleChromecast = new Project("Chromecast", false, parentThree);
parents[0].Children.Add(appleMacBook); parents[0].Children.Add(appleIpad); parents[0].Children.Add(appleiPhone);
parents[1].Children.Add(samsungGalaxy); parents[1].Children.Add(samsungNote);
parents[2].Children.Add(googlePixel); parents[2].Children.Add(googleChromecast);
}
}
}
And here is my XAML where I am trying to display the TreeView. Right now, it is just blank. I would appreciate any tips.
<TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="265"
ItemsSource="{Binding parents}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding parents}" DataType="{x:Type self:Project}">
<TreeViewItem Header="{Binding Name}"></TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Edit:
Here's the Property class:
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
private string name { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildFolders}">
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Icon}" Margin="5, 5, 5, 5"></Image>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" BorderThickness="0" FontSize="16" Margin="5"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
So, this doesn't seem to be firing the change event. I know this because Path is set as Name + ">". When I change the Name, Path is not reflecting the change. It only shows what my previous value for Name was, if that makes sense.
if (ParentFolder == null)
{
Path = Name;
}
else
{
Path = ParentFolder.Path + " > " + Name;
}
Edit:
public Project(string Name, bool isFolder, Project ParentFolder)
{
this.Name = Name;
this.isFolder = isFolder;
Children = new List<Project>();
this.ParentFolder = ParentFolder;
}
public string Path
{
get
{
return this.ParentFolder + " > " + this.Name;
}
set
{
this.Path = Path;
}
}
XAML:
<TextBox x:Name="FolderNameBox" Grid.Column="1" Background="White" Grid.Row="1" Grid.ColumnSpan="5"
Margin="0,0,287,654.333" VerticalContentAlignment="Center"
Padding="6" FontSize="16"
IsReadOnly="True"
Text="{Binding ElementName=Hierarchy, Path=SelectedItem.Path, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<TextBox x:Name="SearchProjectsBox" Grid.Column="5" Background="White" Grid.Row="1" Text="Search Projects"
Margin="47.333,0,0,654.333" VerticalContentAlignment="Center" Foreground="LightGray" Padding="6" FontSize="16" HorizontalAlignment="Left" Width="268" GotFocus="TextBox_GotFocus" LostFocus="TextBox_LostFocus"/>
<TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="226"
ItemsSource="{Binding Projects}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildFolders}">
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Icon}" Margin="5, 5, 5, 5"></Image>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" BorderThickness="0" FontSize="16" Margin="5"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Grid Grid.ColumnSpan="2" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="245,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="540">
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<ListView Margin="0,0,10,0" Name="ProjectView" ItemsSource="{Binding Projects}" FontSize="16" Foreground="Black">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource GridHeader}">
<GridViewColumn Header="Name" Width="200" DisplayMemberBinding="{Binding ElementName=Hierarchy, Path=SelectedItem.Name, UpdateSourceTrigger=PropertyChanged}"></GridViewColumn>
<GridViewColumn Header="Directory" Width="328" DisplayMemberBinding="{Binding ElementName=Hierarchy, Path=SelectedItem.Path, UpdateSourceTrigger=PropertyChanged}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
</Grid>
</Grid>
The Path updates too but when it I see it it will display the path of the project rather than the fired change of name. It changes in real-time but doesn't save the String value..only registers that a change has happened.
Heres my Property Change too.
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You have a few problems here.
ItemsSource="{Binding parents}"
Here's parents:
private void BuildData()
{
List<Project> parents = new List<Project>();
You're asking XAML to examine all the methods in the codebehind class, looking for local variables named parents. This isn't a reasonable request.
There are a few requirements if you want to bind to parents: It must be...
A public...
Property (not a field -- it needs a get block)...
Of whatever object is your TreeView's DataContext.
None of those are true.
Two more things -- not required, but a very good idea:
Make it ObservableCollection<T> rather than List<T>, so that it will notify the UI of added or removed items.
The class that owns it should be a viewmodel class, not your window/usercontrol. When we say "viewmodel", we mean it implements INotifyPropertyChanged and raises PropertyChanged when its property values change. Again, this is about keeping the UI informed of changes.
Keeping the UI informed is what bindings are all about: They listen for changes in the viewmodel and update the UI.
So you need a main viewmodel that looks like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// C#6
/*
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
*/
protected virtual void OnPropertyChanged(string propName)
{
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propName));
}
}
}
public class MainViewModel : ViewModelBase
{
private ObservableCollection<Project> _projects;
public ObservableCollection<Project> Projects {
get { return _projects; }
set {
if (value != _projects) {
_projects = value;
OnPropertyChanged(nameof(Projects));
}
}
}
public void BuildData() {
Projects = new ObservableCollection<Project>();
// do the rest of the stuff
}
}
And you should rewrite your Project class as a ProjectViewModel derived from ViewModelBase, make it raise PropertyChanged in the same way, and use ObservableCollection<Project> for Children.
And in your main window...
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
vm.BuildData();
DataContext = vm;
}
Your XAML needs a little work, too.
Projects has a capitalized name now
For the item template, you are binding to the property of the child item which provides the tree view item's children. That's the Children property of your Project class.
A datatemplate tells XAML how to present the content of a control. The tree creates a TreeViewItem with a Project as its DataContext, and then uses your HierarchicalDataTemplate to turn that DataContext into some kind of visual content. You don't use the template to create a TreeViewItem; you use it to create the visual stuff in the TreeViewItem.
So here's the new XAML:
<TreeView
x:Name="Hierarchy"
ItemsSource="{Binding Projects}"
Grid.Column="4"
HorizontalAlignment="Left"
Height="631"
Margin="0,58,0,0"
Grid.Row="1"
VerticalAlignment="Top"
Width="265"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Label Content="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
There's no reason to get in the habit of DataContext = this;. Once you start that, the next thing you know you'll be doing it in a UserControl and coming here asking why all your bindings to it in the parent XAML are broken. Dependency properties are a bigger hassle than INPC, and you end up with code that ought to be in a viewmodel mixed into your MainWindow code. If you use viewmodelsit's the easiest thing in the world to shuffle your UI around. Maybe you want the original content of your main window to be just one of three tab pages in the main window. Keeping code separated properly makes that kind of thing much simpler.
I had a research by criteria with Two combobox, it works fine
after the research is finished, I have a button Display All : to reset the combobox to null..and the DataGrid display with all elements ,
The problem that the combobox must be empty when I click on the Button Dispaly All!
Without select an element in combobox(just dispaly the datagrid):I have 6 elements in the datagrid, it is correct..and the combobox are Empty
After select the Search criteria, i have the result correct: (I have just 3 results, it is the correct action)
3 elements picture
When I click on the button Display All:(I have all the elements in datagrid, 6 elements..It is correct) But the Combobox aren't empty!!
6 elements picture
The view:
<Window x:Class="WPFAuthentification.Views.BusinesseventsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" >
<Label Content="Entity Type" Width="128" Grid.Row="1" Grid.ColumnSpan="2"/>
<ComboBox HorizontalAlignment="Center" VerticalAlignment="Center"
ItemsSource="{Binding EntityLevelEnum}"
SelectedItem="{Binding EntityType, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, TargetNullValue=''}"
Grid.ColumnSpan="2" Grid.Column="1" />
<Button Content="Dislplay all" ToolTip="Display All Business Events"
VerticalAlignment="Top" Command="{Binding Initialize}"
Visibility="{Binding Path=ShowDisplayAllButton, Converter={StaticResource BoolToVis}}" />
<DataGrid ..... />
</Window>
The ViewModel:
class BusinesseventsViewModel : ViewModelBase1
{
private ObservableCollection<BusinessEventClass> businessEventsList;
private RelayCommand<string> initialize;
public RelayCommand<string> Initialize
{
get { return initialize; }
}
public BusinesseventsViewModel()
{
//businessEventsList: to Get all the Business events
businessEventsList = new ObservableCollection<BusinessEventClass>(WCFclient.getAllBusinessEvent());
//Enumeration of Entity Type and Criticality
levelCriticalityEnum = new ObservableCollection<Level_Criticality>(Enum.GetValues(typeof(Level_Criticality)).Cast<Level_Criticality>());
entityLevelEnum = new ObservableCollection<BusinessEntityLevel>(Enum.GetValues(typeof(BusinessEntityLevel)).Cast<BusinessEntityLevel>());
//the Button Display All :
initialize = new RelayCommand<string>(initFunc);
}
//Function of the Button Display All
private void initFunc(object obj)
{
EntityType = null;
OnPropertyChanged("EntityLevelEnum");
Criticality = null;
OnPropertyChanged("Criticality");
}
private string entityType;
public string EntityType
{
get { return entityType; }
set
{
entityType = value;
businessEventsList = filterByCriteria(entityType, criticality);
OnPropertyChanged("BusinessEventsList");
OnPropertyChanged("EntityType");
}
}
//Function of the research :
public ObservableCollection<BusinessEventClass> filterByCriteria(string entityType, string criticality)
{
BusinessEventsList = new ObservableCollection<BusinessEventClass>(WCFclient.getAllBusinessEvent());
ObservableCollection<BusinessEventClass> updatedList = new ObservableCollection<BusinessEventClass>();
if ((entityType == null) && (Criticality == null))
{
updatedList = businessEventsList;
}
if ((entityType != null && entityType != "") && (Criticality != null))
{
updatedList = new ObservableCollection<BusinessEventClass>(BusinessEventsList.Where(a => a.EntityType.ToString().ToLower().Equals(criticality.ToString())
&& a.Critciality.ToString().Equals(criticality.ToString())));
}
}
There must be something wrong with your implementation of the INotifyPropertyChanged interface.
Using GalaSoft.MvvmLight I did this, and works without problem;
Window.cs content:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new TestViewModel();
}
}
public class TestViewModel : ViewModelBase
{
private string _selectedItem;
public RelayCommand Command { get; }
public ObservableCollection<string> ItemsSource { get; }
public string SelectedItem
{
get { return _selectedItem; }
set { Set(ref _selectedItem, value); }
}
public TestViewModel()
{
Command = new RelayCommand(() => SelectedItem = null);
ItemsSource = new ObservableCollection<string> { "index 0", "index 1", "index 2", "index 3" };
}
}
and my Window.xaml content
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type testClean:TestViewModel}">
<Grid>
<Viewbox>
<TextBlock Foreground="HotPink">just some pink text</TextBlock>
</Viewbox>
<ComboBox Height="50" Width="200" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="20" SelectedIndex="0"
ItemsSource="{Binding ItemsSource}"
SelectedItem="{Binding SelectedItem}"/>
<Button Command="{Binding Command}" Height="50" Width="100" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="250,20,20,20">Reset</Button>
</Grid>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<ContentControl Content="{Binding}" />
Tested and working as expected (when you put null as SelectedItem the combobox returns empty)
I'm facing a problem in my WPF project at the moment. At this moment I have a Viewmodel which has a Manager (to communicate with the repo).
internal class TicketViewModel
{
private TicketManager mgr;
public IEnumerable<Ticket> List { get; set; }
public TicketViewModel()
{
mgr = new TicketManager();
List = mgr.GetTickets();
}
}
I've managed to bind this list to the Listbox in my MainWindow. The next step is that I need to add an extra ticket to the list and also pass this through the manager. The problem is I need two parameters from some Controls in the MainWindow. From MVVM perspective I need to use bound Commands on e.g. a Button to communicate with the viewmodel as my viewmodel can't/may not access controls from the window. Is using parameterized Commands the way to go here?
The next problem is that the Listbox won't update I guess. This is the code:
<ListBox x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I found that using a CompareableCollection is the way to go here, but then I still have to read all the Tickets again after adding a new Ticket.
Thanks in advance,
Hicy
okay here is the code.
Lets say you have three textboxes on MainWindow(since you have three Textblocks.) so Your MainWindow.xaml looks like
<Window.DataContext>
<local:MyViewModel/>--set's your viewModel
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="250*"/>
<RowDefinition Height="90"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="TicketNumber" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=Text}" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="Text" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=State}" />
<TextBox x:Name="State" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=TicketNumber}" />
<Button Content="Button" Command="{Binding Path=MainCommand}" Grid.Row="2"/>
</Grid>
and I am assuming that you have some class called class Ticket which contain these three members
Class Ticket
{
public int TicketNumber { get; set; }
public string Text { get; set; }
public string State { get; set; }
}
Now in class TicketManager we fill it with some dummy data
class TicketManager
{
ObservableCollection<Ticket> tl = new ObservableCollection<Ticket>();
internal ObservableCollection<Ticket> GetTickets()
{
tl.Add(new Ticket() { State = "State1", Text = "Text1", TicketNumber = 1 });
tl.Add(new Ticket() { State = "State2", Text = "Text2", TicketNumber = 2 });
tl.Add(new Ticket() { State = "State3", Text = "Text3", TicketNumber = 3 });
return tl;
}
}
and in your Mainwindow ViewModel lets call it MyViewModel.cs we add
class MyViewModel:INotifyPropertyChanged
{
private TicketManager mgr;
public ObservableCollection<Ticket> List { get; set; }
private string text;
private string state;
private int ticketNumber;
private readonly DelegateCommand<object> MyButtonCommand;
public Class1()
{
mgr = new TicketManager();
List = mgr.GetTickets();
MyButtonCommand = new DelegateCommand<object>((s) => { AddListToGrid(text, state, ticketNumber); }, (s) => { return !string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(state); });
}
private void AddListToGrid(string text, string state, int ticketNumber)
{
List.Add(new Ticket() {Text=text,State=state,TicketNumber=ticketNumber });
}
public DelegateCommand<object> MainCommand
{
get
{
return MyButtonCommand;
}
}
public string Text
{
get
{
return text;
}
set
{
text = value;
OnPropertyChanged("Text");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public string State
{
get
{
return state;
}
set
{
state = value;
OnPropertyChanged("State");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public int TicketNumber
{
get
{
return ticketNumber;
}
set
{
ticketNumber = value;
OnPropertyChanged("TicketNumber");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can Modify the code in anyway you want
This ViewModel implements fewthings which are very important from MVVM point of view
1) INotifyPropertyChanged
2) WPF Delegate Command
P.S:The code is tested and it runs as expected
Don't get hung up on MVVM it is simply a separation of data from a view, and models are shared between the two with a majority of the business logic (on a shared component) should be performed on the VM; it is not a religion just a three tiered data system. IMHO
If your button needs to do an operation, have it make a call, most likely in the code behind, to a method on the VM which handles the business logic, updates the list with the new item and notifies the manager.
I would bind the list in question to an ObservableCollection which can notify upon insert/delete of an item.
I am trying to bind a ListView with a Model List. The xaml is as -
<ListView Name="lvProductBinding" HorizontalAlignment="Left" Height="434" Margin="10,144,0,0" VerticalAlignment="Top" Width="909">
<ListView.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding ProductNo}" HorizontalAlignment="Left" Margin="967,153,-912,0" VerticalAlignment="Top" Width="895" Height="224" IsExpanded="False">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Height="195" VerticalAlignment="Top" Width="895" Margin="0,0,-2,0">
<Label Content="{Binding ProductDescription}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="181" Height="27" />
<Label Content="{Binding VendorName}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="181" Height="27" />
</StackPanel>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In mu xaml.cs I am doing this inside the constructor -
List<ProductDetailsModel> products;
products = new List<ProductDetailsModel>();
ProductDetailsModel objProductDetailsModel = new ProductDetailsModel();
objProductDetailsModel.VendorProductInventory = new VendorProductInventory() { ProductNo = "XS-3487", ProductDescription = "Perfume", VendorName = "JohnDoe" };
products.Add(objProductDetailsModel);
objProductDetailsModel = new ProductDetailsModel();
objProductDetailsModel.VendorProductInventory = new VendorProductInventory() { ProductNo = "TT-23487", ProductDescription = "Shoes", VendorName = "Richard Gere" };
products.Add(objProductDetailsModel);
objProductDetailsModel = new ProductDetailsModel();
objProductDetailsModel.VendorProductInventory = new VendorProductInventory() { ProductNo = "VFG-33487", ProductDescription = "Socks", VendorName = "Tom Cruise" };
products.Add(objProductDetailsModel);
lvProductBinding.ItemsSource = products;
where the ProductDetailsModel class is defined as -
public class ProductDetailsModel : INotifyPropertyChanged
{
public ProductDetailsModel()
{
}
private VendorProductInventory _vendorProductInventory;
public VendorProductInventory VendorProductInventory
{
get
{
return _vendorProductInventory;
}
set
{
if (_vendorProductInventory != value)
{
_vendorProductInventory = value;
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
}
Can someone please advise as to what I am doing wrong here.
Eagerly waiting for a reply.
Thanks,
Saket
There are several things wrong with the code you posted.
First, as Sheridan noted, your implementation of INotifyPropertyChanged is wrong, automatic updates to the data in the ListView won't work until it is correct.
Second, all of the bindings listed in your XAML file don't have matching public properties in the ProductDetailsModel class. More than likely your ListView is being populated and all the bindings are failing (you should see exceptions for this in the VS output window at runtime). Your bindings, as written, should look like:
{Binding Path=VendorProductInventory.ProductNo}
I am not able to figure out why my third Nested DataBinding in WPF is not working. I am using Entity Framework and Sql Server 2012 and following are my entities. An Application can have more than one accounts. There is an Accounts Table and an Applications Table.
ENTITIES
1. Applications
2. Accounts
VIEWMODELS
1. ApplicationListViewModel
2. ApplicationViewModel
3. AccountListViewModel
4. AccountViewModel
In my usercontrol I am trying to do following:
1. Use combobox to select an application using ApplicationListViewModel (Working)
2. Upon selected application display all accounts in datagrid (Working)
3. Upon selected account display details information about a particular account.(Does not show details of the selected account)
<UserControl.Resources>
<vm:ApplicationListViewModel x:Key="AppList" />
</UserControl.Resources>
<StackPanel DataContext="{Binding Source={StaticResource AppList}}">
<Grid>
<Grid.RowDefinitions>
...
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0">
<GroupBox Header="View all">
<StackPanel>
<!-- All Applications List -->
<ComboBox x:Name="cbxApplicationList"
ItemsSource="{Binding Path=ApplicationList}"
DisplayMemberPath="Title" SelectedValuePath="Id"
SelectedItem="{Binding Path=SelectedApplication, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True" />
<!-- Selected Application Accounts -->
<DataGrid x:Name="dtgAccounts" Height="Auto" Width="auto" AutoGenerateColumns="False"
DataContext="{Binding SelectedApplication.AccountLVM}"
ItemsSource="{Binding Path=AccountList}"
SelectedItem="{Binding SelectedAccount, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Title" Binding="{Binding Path=Title}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</GroupBox>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" >
<GroupBox x:Name="grpBoxAccountDetails" Header="New Account" >
<!-- Selected Account Details -->
<!-- DataContext binding does not appear to work -->
<StackPanel DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}" >
<Grid>
<Grid.RowDefinitions>
...
</Grid.ColumnDefinitions>
<TextBlock x:Name="lblApplication" Grid.Row="0" Grid.Column="0" >Application</TextBlock>
<ComboBox x:Name="cbxApplication" Grid.Row="0" Grid.Column="1"
DataContext="{Binding Source={StaticResource AppList}}"
ItemsSource="{Binding ApplicationList}"
DisplayMemberPath="Title" SelectedValuePath="Id"
SelectedValue="{Binding SelectedApplication.AccountLVM.SelectedAccount.ApplicationId}">
</ComboBox>
<TextBlock x:Name="lblTitle" Grid.Row="0" Grid.Column="0" >Title</TextBlock>
<TextBox x:Name="txtTitle" Grid.Row="0" Grid.Column="1" Height="30" Width="200"
Text="{Binding Title}" DataContext="{Binding Mode=OneWay}"></TextBox>
<Button Grid.Row="1" Grid.Column="0" Command="{Binding AddAccount}">Add</Button>
</Grid>
</StackPanel>
</GroupBox>
</StackPanel>
</Grid>
</StackPanel>
ApplicationListViewModel
class ApplicationListViewModel : ViewModelBase
{
myEntities context = new myEntities();
private static ApplicationListViewModel instance = null;
private ObservableCollection<ApplicationViewModel> _ApplicationList = null;
public ObservableCollection<ApplicationViewModel> ApplicationList
{
get
{
return GetApplications();
}
set {
_ApplicationList = value;
OnPropertyChanged("ApplicationList");
}
}
//public ObservableCollection<ApplicationViewModel> Cu
private ApplicationViewModel selectedApplication = null;
public ApplicationViewModel SelectedApplication
{
get
{
return selectedApplication;
}
set
{
selectedApplication = value;
OnPropertyChanged("SelectedApplication");
}
}
//private ICommand showAddCommand;
public ApplicationListViewModel()
{
this._ApplicationList = GetApplications();
}
internal ObservableCollection<ApplicationViewModel> GetApplications()
{
if (_ApplicationList == null)
_ApplicationList = new ObservableCollection<ApplicationViewModel>();
_ApplicationList.Clear();
foreach (Application item in context.Applications)
{
ApplicationViewModel a = new ApplicationViewModel(item);
_ApplicationList.Add(a);
}
return _ApplicationList;
}
public static ApplicationListViewModel Instance()
{
if (instance == null)
instance = new ApplicationListViewModel();
return instance;
}
}
ApplicationViewModel
class ApplicationViewModel : ViewModelBase
{
private myEntities context = new myEntities();
private ApplicationViewModel originalValue;
public ApplicationViewModel()
{
}
public ApplicationViewModel(Application acc)
{
//Initialize property values
this.originalValue = (ApplicationViewModel)this.MemberwiseClone();
}
public ApplicationListViewModel Container
{
get { return ApplicationListViewModel.Instance(); }
}
private AccountListViewModel _AccountLVM = null;
public AccountListViewModel AccountLVM
{
get
{
return GetAccounts();
}
set
{
_AccountLVM = value;
OnPropertyChanged("AccountLVM");
}
}
internal AccountListViewModel GetAccounts()
{
_AccountLVM = new AccountListViewModel();
_AccountLVM.AccountList.Clear();
foreach (Account i in context.Accounts.Where(x=> x.ApplicationId == this.Id))
{
AccountViewModel account = new AccountViewModel(i);
account.Application = this;
_AccountLVM.AccountList.Add(account);
}
return _AccountLVM;
}
}
AccountListViewModel
class AccountListViewModel : ViewModelBase
{
myEntities context = new myEntities();
private static AccountListViewModel instance = null;
private ObservableCollection<AccountViewModel> _accountList = null;
public ObservableCollection<AccountViewModel> AccountList
{
get
{
if (_accountList != null)
return _accountList;
else
return GetAccounts();
}
set {
_accountList = value;
OnPropertyChanged("AccountList");
}
}
private AccountViewModel selectedAccount = null;
public AccountViewModel SelectedAccount
{
get
{
return selectedAccount;
}
set
{
selectedAccount = value;
OnPropertyChanged("SelectedAccount");
}
}
public AccountListViewModel()
{
this._accountList = GetAccounts();
}
internal ObservableCollection<AccountViewModel> GetAccounts()
{
if (_accountList == null)
_accountList = new ObservableCollection<AccountViewModel>();
_accountList.Clear();
foreach (Account item in context.Accounts)
{
AccountViewModel a = new AccountViewModel(item);
_accountList.Add(a);
}
return _accountList;
}
public static AccountListViewModel Instance()
{
if (instance == null)
instance = new AccountListViewModel();
return instance;
}
}
AccountViewModel. I am eliminating all other initialization logic aside in viewmodel for simplicity.
class AccountViewModel : ViewModelBase
{
private myEntites context = new myEntities();
private AccountViewModel originalValue;
public AccountViewModel()
{
}
public AccountViewModel(Account acc)
{
//Assign property values.
this.originalValue = (AccountViewModel)this.MemberwiseClone();
}
public AccountListViewModel Container
{
get { return AccountListViewModel.Instance(); }
}
public ApplicationViewModel Application
{
get;
set;
}
}
Edit1:
When I data bind to view the details of the SelectedAccount with textbox it doesn't show any text.
1. Able to databind to ApplicationListViewModel to Combobox.
2. Successfully Bind to view AccountList based upon SelectedApplication
3. Unable to Bind to SelectedAcount in the AccountListViewModel.
I think in the following line it doesn't show any details about the selected account. I have checked all databinding syntax. In the properties I am able to view appropriate DataContext and bind to the properties. But it doesn't show any text. When I select each individual record in the DataGrid I am able to debug the call and select the object but somehow that object is not being shown in the textbox at the very end.
DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}"
Edit2:
Based upon the suggestion in the comment below I tried snoop and was able to see the title textbox row highlighted in red color. I am trying to change the binding Path property and datacontext but still not working. When I tried to click on the "Delve Binding Expression" it gave me unhandled exception. I don't know what that means if as it came from Snoop.
Edit3:
I have taken screenshots of DataContext Property for the StackPanel for the Account Details section and the text property of the textbox.
Solution:
Based upon suggestions below I have made following changes to my solution and made it way more simple. I made it unnecessarily complex.
1. AccountsViewModel
2. AccountViewModel
3. ApplicationViewModel
Now I have created properties as SelectedApplication, SelectedAccount all in just one AccountsViewModel. Removed all complex DataContext syntax and now there is just one DataContext in the xaml page.
Simplified code.
class AccountsViewModel: ViewModelBase
{
myEntities context = new myEntities();
private ObservableCollection<ApplicationViewModel> _ApplicationList = null;
public ObservableCollection<ApplicationViewModel> ApplicationList
{
get
{
if (_ApplicationList == null)
{
GetApplications();
}
return _ApplicationList;
}
set
{
_ApplicationList = value;
OnPropertyChanged("ApplicationList");
}
}
internal ObservableCollection<ApplicationViewModel> GetApplications()
{
if (_ApplicationList == null)
_ApplicationList = new ObservableCollection<ApplicationViewModel>();
else
_ApplicationList.Clear();
foreach (Application item in context.Applications)
{
ApplicationViewModel a = new ApplicationViewModel(item);
_ApplicationList.Add(a);
}
return _ApplicationList;
}
//Selected Application Property
private ApplicationViewModel selectedApplication = null;
public ApplicationViewModel SelectedApplication
{
get
{
return selectedApplication;
}
set
{
selectedApplication = value;
this.GetAccounts();
OnPropertyChanged("SelectedApplication");
}
}
private ObservableCollection<AccountViewModel> _accountList = null;
public ObservableCollection<AccountViewModel> AccountList
{
get
{
if (_accountList == null)
GetAccounts();
return _accountList;
}
set
{
_accountList = value;
OnPropertyChanged("AccountList");
}
}
//public ObservableCollection<AccountViewModel> Cu
private AccountViewModel selectedAccount = null;
public AccountViewModel SelectedAccount
{
get
{
return selectedAccount;
}
set
{
selectedAccount = value;
OnPropertyChanged("SelectedAccount");
}
}
internal ObservableCollection<AccountViewModel> GetAccounts()
{
if (_accountList == null)
_accountList = new ObservableCollection<AccountViewModel>();
else
_accountList.Clear();
foreach (Account item in context.Accounts.Where(x => x.ApplicationId == this.SelectedApplication.Id))
{
AccountViewModel a = new AccountViewModel(item);
_accountList.Add(a);
}
return _accountList;
}
}
XAML Side
<UserControl.Resources>
<vm:AccountsViewModel x:Key="ALVModel" />
</UserControl.Resources>
<StackPanel DataContext="{Binding Source={StaticResource ALVModel}}" Margin="0,0,-390,-29">
<StackPanel>
<ComboBox x:Name="cbxApplicationList"
ItemsSource="{Binding Path=ApplicationList}"
DisplayMemberPath="Title" SelectedValuePath="Id"
SelectedItem="{Binding Path=SelectedApplication, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True"></ComboBox>
<DataGrid x:Name="dtgAccounts" Height="Auto" Width="auto"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=AccountList}"
SelectedItem="{Binding SelectedAccount, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="Title" Binding="{Binding Path=Title}"></DataGridTextColumn>
<DataGridTextColumn Header="CreatedDate" Binding="{Binding Path=CreatedDate}"></DataGridTextColumn>
<DataGridTextColumn Header="LastModified" Binding="{Binding Path=LastModifiedDate}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<StackPanel Height="Auto" Width="300" HorizontalAlignment="Left" DataContext="{Binding Path=SelectedAccount}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="200"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock x:Name="lblTitle" Grid.Row="0" Grid.Column="0" >Title</TextBlock>
<TextBox x:Name="txtTitle" Grid.Row="0" Grid.Column="1" Height="30" Width="200"
Text="{Binding Title}"></TextBox>
</Grid>
</StackPanel>
</StackPanel>
I didn't understood MVVM concept properly. I tried to build everything modular and in the end I screwed it up.
I suspect your problem is related to the fact you are returning a new ObservableCollection every time you call the setter for AccountLVM, and you are not raising your PropertyChange notification, so any existing bindings do not get updated
public AccountListViewModel AccountLVM
{
get
{
return GetAccounts();
}
set
{
_AccountLVM = value;
OnPropertyChanged("AccountLVM");
}
}
internal AccountListViewModel GetAccounts()
{
_AccountLVM = new AccountListViewModel();
_AccountLVM.AccountList.Clear();
foreach (Account i in context.Accounts.Where(x=> x.ApplicationId == this.Id))
{
AccountViewModel account = new AccountViewModel(i);
account.Application = this;
_AccountLVM.AccountList.Add(account);
}
return _AccountLVM;
}
I find your bindings very confusing and hard to follow, however I think whenever this gets evaluated
DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}"
it is creating a new AccountLVM, which does not have the SelectedAccount property set.
You don't see the existing DataGrid.SelectedItem change at all because it's still bound to the old AccountLVM as no PropertyChange notification got raised when _accountLVM changed, so the binding doesn't know to update.
But some other miscellaneous related to your code:
Don't change the private version of the property unless you also raise the PropertyChange notification for the public version of the property. This applies to both your constructors and your GetXxxxx() methods like GetAccounts().
Don't return a method call from your getter. Instead set the value using your method call if it's null, and return the private property afterwards.
public AccountListViewModel AccountLVM
{
get
{
if (_accountLVM == null)
GetAccounts(); // or _accountLVM = GetAccountLVM();
return _accountLVM;
}
set { ... }
}
It's really confusing to have the DataContext set in so many controls. The DataContext is the data layer behind your UI, and it's easiest if your UI simply reflects the data layer, and having to go all over the place to get your data makes the data layer really hard to follow.
If you must make a binding to something other than the current data context, try to use other binding properties to specify a different binding Source before immediately going to change the DataContext. Here's an example using the ElementName property to set the binding source:
<TextBox x:Name="txtTitle" ...
Text="{Binding ElementName=dtgAccounts, Path=SelectedItem.Title}" />
The DataContext in inherited, so you don't ever need to write DataContext="{Binding }"
You may want to consider re-writing your parent ViewModel so you can setup XAML like this, without all the extra DataContext bindings or 3-part nested properties.
<ComboBox ItemsSource="{Binding ApplicationList}"
SelectedItem="{Binding SelectedApplication}" />
<DataGrid ItemsSource="{Binding SelectedApplication.Accounts}"
SelectedItem="{Binding SelectedAccount}" />
<StackPanel DataContext="{Binding SelectedAccount}">
...
</StackPanel>
If you're new to the DataContext or struggling to understand it, I'd recommend reading this article on my blog to get a better understanding of what it is and how it works.
Well one major problem with this Binding method is, that the value is only updated, when the last property value, in your case SelectedAccount, is changed. The other levels are not watched by the BindingExpression, so if e.g. SelectedApplication.AccountLVM is changed the DataContext will not notice a difference in SelectedAccount because the binding is still 'watching' on the old reference and you're modifying another reference in your VM.
So I think at the start of the application SelectedApplication is null and the Binding of the ComboBox doesn't notice that it changes. Hmm, I thought about another binding solution, but I couldn't found one. So I suggest, that you create an additional property for reflecting SelectedAccount in your ApplicationListViewModel class.