Comment section inside each ListBox items - c#

I'm developing an external application in C#/WPF which display information : Name, Details. Etc...
I handle this part with a ListBox and simple Binding. I populate my ListBox this way :
model.ClassAs.Add(new ClassA { Name = textBox1.Text, Detail = textBox2.Text });
The problem come now : I now have to implement a Comment Section for users to add comments inside each ListBoxItems separately. Obviously I can't fill the Comment Section when i populate Name and Details. So i create a second ListBox inside the ItemsTemplate of the first one, a ListBoxSeption if you want. I create an other Class ClassB and try to implement the Binding like the first time.
Obviously that did not work because i was in the DataContext of ListBox1. I try to set up an ObservableCollection with a PropertyChanged Event but i can't get it to work. I don't understand this process very well so i don't see where my error is.
Here is my XAML:
<ListBox x:Name="listBox" MouseDoubleClick="ListBox_MouseDoubleClick"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" HorizontalContentAlignment="Stretch"
ItemsSource="{Binding ClassAs, Mode=OneWay}" Grid.Column="1" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="grd">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Name="Name" Text="{Binding Name, Mode=OneWay}" Grid.Column="2" Foreground="DarkGray" FontWeight="Bold" />
<TextBlock Text="{Binding Detail, Mode=OneWay}" Grid.Column="2" Grid.Row="1" TextWrapping="Wrap" />
<Grid x:Name="comSection" Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected, Converter={StaticResource booleanVisibleConverter}}"
Grid.Column="2" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<AdornerDecorator>
<TextBox x:Name="textBoxCom" HorizontalAlignment="Stretch"
TextWrapping="Wrap" Grid.Column="0" Grid.Row="0">
<controls:WatermarkService.Watermark>
<TextBlock>Type Comment Here...</TextBlock>
</controls:WatermarkService.Watermark>
</TextBox>
</AdornerDecorator>
<ListBox x:Name="listBoxCom" BorderThickness="0,0,0,0" Grid.Row="1" Grid.Column="0"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" VerticalAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Visible"
ItemsSource="{Binding ClassBs.Commentary}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<TextBlock Name="TBCom" Text="{Binding}"></TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<Button x:Name="addComment" HorizontalAlignment="Center" VerticalAlignment="Top" Width="25" Height="25"
BorderThickness="0" Click="addComment_Click" Background="Transparent" Grid.Column="1" Grid.Row="2"
ToolTip="Comment">
<Button.Content>
<Image Source="Assets\plus_orange.png" />
</Button.Content>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What i want : The user fill textBoxCom and Press addComment Button to add a Comment in the selected ListBoxItem.
My Classes :
public sealed class ClassA : INotifyPropertyChanged
{
public string Name { get; set; }
public string Detail { get; set; }
public ObservableCollection<ClassB> ClassBs { get; set; }
public List<ClassB> ModifiedComments { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public class ClassB
{
string Commentary { get; set;}
}
}
My ViewModel :
public sealed class ViewModel
{
public ObservableCollection<ClassA> ClassAs { get; set; }
public ObservableCollection<ClassA.ClassB> ClassBs { get; set; }
List<ClassA.ClassB> ModifiedItems { get; set; }
public ViewModel()
{
ClassAs = new ObservableCollection<ClassA>();
CLassBs = new ObservableCollection<ClassA.ClassB>();
ModifiedItems = new List<ClassA.ClassB>();
this.ClassBs.CollectionChanged += this.OnCollectionChanged;
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
try
{
if (e.NewItems != null)
{
foreach (ClassA.ClassB newItem in e.NewItems)
{
ModifiedItems.Add(newItem);
//Add listener for each item on PropertyChanged event
newItem.PropertyChanged += this.OnItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (ClassA.ClassB oldItem in e.OldItems)
{
ModifiedItems.Add(oldItem);
oldItem.PropertyChanged -= this.OnItemPropertyChanged;
}
}
}
catch(Exception ex)
{
MessageBox.Show("me : " + ex);
}
}
void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
ClassA.ClassB item = sender as ClassA.ClassB;
if (item != null)
ModifiedItems.Add(item);
}
}
My Click Event (AddComment)
private void addComment_Click(object sender, RoutedEventArgs e)
{
try
{
currentSelectedListBoxItem = this.listBox.ItemContainerGenerator.ContainerFromIndex(listBox.SelectedIndex) as ListBoxItem;
System.Windows.Controls.TextBox textBoxCom = Helper.FindDescendant<System.Windows.Controls.TextBox>(currentSelectedListBoxItem);
ListBox LBCom = Helper.FindDescendant<ListBox>(currentSelectedListBoxItem);
if (string.IsNullOrWhiteSpace(textBoxCom.Text))
MessageBox.Show("Please fill the Comment Section");
else
{
model.ClassBs.Add(new ClassA.ClassB { Commentary = textBoxCom.Text });
textBoxCom.Clear();
}
}
catch (Exception exp)
{
MessageBox.Show("exp = " + exp);
}
}
Sorry for the very long post, i hope you can help my on this !

Well this was way simpler than expected, i was just digging in useless code!
I get rid of all the code about ObservableCollection and PropertyChange Event.
My final ClassA Code (no more ClassB) :
public sealed class ClassA
{
public string Name { get; set; }
public string Detail { get; set; }
public List<string> Comments { get; set; }
public Issue()
{
Comments = new List<string>();
}
}
And my final Click Event :
var selected = listBox.SelectedItem as ClassA;
selected.Comments.Add(textBoxCom.Text);
listBox.Items.Refresh();
textBoxCom.Clear();
Thank you #Sidewinder94 !

Related

Cannot insert duplicate key in object 'dbo.Addresses'

I have been struggling for weeks with my WPF appliaction named "ContactManager" when i want to add records to the database.
I have two entites:
public partial class Contact : EntityBase
{
public int ContactId { get; set; }
[StringLength(20)]
public string FirstName { get; set; }
[StringLength(20)]
public string LastName { get; set; }
[StringLength(20)]
public string Organization { get; set; }
[StringLength(20)]
public string JobTitle { get; set; }
public string ImagePath { get; set; }
public string CellPhone { get; set; }
public string HomePhone { get; set; }
public string OfficePhone { get; set; }
public string PrimaryEmail { get; set; }
public string SecondaryEmail { get; set; }
public virtual Address Address { get; set; } = new Address();
public class Address : EntityBase
{
[ForeignKey("Contact")]
public int AddressId { get; set; }
public string City { get; set; }
public string Country { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Zip { get; set; }
public string State { get; set; }
public virtual Contact Contact { get; set; }
The add method:
public int Add(Contact entity)
{
Context.Contacts.Add(entity);
var o = SaveChanges();
Context.Dispose();
Context = new CMEntities();
return o;
}
internal int SaveChanges()
{
try
{
return Context.SaveChanges();
}
}
When the database is empty it is working but after closing and restarting the app I get the following exception:
SqlException: Violation of PRIMARY KEY constraint 'PK_dbo.Addresses'. Cannot insert duplicate key in object 'dbo.Addresses'. The duplicate key value is (1).
The statement has been terminated.
I do not understand why Sql server (or entity framework??) wants to insert key 1 instead of the next id...
if i delete rows in table Addresses it is working again, it inserts the row without problem with the next/right addressid.( that is the addressid is not 1 but 5 because of the previous rows of contacts )
In my opinion the schema of( keys etc ) tables are correct the problem is with other issue, maybe binding or context i have not got a clue...
(I made a simpler example omitting MVC, it is working. )
Here is the code of MVC app:
namespace CMnew.Model
{
public partial class ContactRepository : IDisposable
{
public CMEntities Context { get; set; } = new CMEntities();
private List<Contact> _contactStore;
public ContactRepository()
{
if (this.GetAll() == null)
{
_contactStore = new List<Contact>();
}
else
{
_contactStore = this.GetAll();
}
}
public List<Contact> FindByLookup(string lookupName)
{
IEnumerable<Contact> found = from c in _contactStore
where c.LookupName.StartsWith(lookupName, StringComparison.OrdinalIgnoreCase)
select c;
return found.ToList();
}
public List<Contact> FindAll()
{
return new List<Contact>(_contactStore);
}
public void Save(Contact contact)
{
if (_contactStore.Contains(contact))
{
this.SaveToDatabase(contact);
}
else
{
_contactStore.Add(contact);
this.Add(contact);
}
}
public int SaveToDatabase(Contact entity)
{
return SaveChanges();
}
public void Delete(Contact contact)
{
_contactStore.Remove(contact);
DeleteFromDatabase(contact);
}
public int DeleteFromDatabase(Contact entity)
{
Context.Entry(entity).State = EntityState.Deleted;
return SaveChanges();
}
public Contact GetOne(int? id) => Context.Contacts.Find(id);
public List<Contact> GetAll() => Context.Contacts.ToList();
public int Add(Contact entity)
{
Context.Contacts.Add(entity);
var o = SaveChanges();
Context.Dispose();
Context = new CMEntities();
return o;
}
internal int SaveChanges()
{
try
{
return Context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
throw;
}
catch (DbUpdateException ex)
{
throw;
}
catch (CommitFailedException ex)
{
throw;
}
catch (Exception ex)
{
throw ex;
}
}
bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}
if (disposing)
{
Context.Dispose();
}
disposed = true;
}
}
}
namespace CMnew.Presenters
{
public class ApplicationPresenter : PresenterBase<Shell>, INotifyPropertyChanged
{
private readonly ContactRepository _contactRepository;
private ObservableCollection<Contact> _currentContacts;
public event PropertyChangedEventHandler PropertyChanged;
public ApplicationPresenter(Shell view, ContactRepository contactRepository) : base(view)
{
_contactRepository = contactRepository;
_currentContacts = new ObservableCollection<Contact>(_contactRepository.FindAll());
}
// public ObservableCollection<Contact> CurrentContacts { get; set; }
public ObservableCollection<Contact> CurrentContacts
{
get { return _currentContacts; }
set
{
_currentContacts = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentContacts)));
}
}
public string StatusText { get; set; }
public void Search(string criteria)
{
if (!string.IsNullOrEmpty(criteria) && criteria.Length > 2)
{
CurrentContacts = new ObservableCollection<Contact>(_contactRepository.FindByLookup(criteria));
StatusText = string.Format("{0} contacts found.", CurrentContacts.Count);
}
else
{
CurrentContacts = new ObservableCollection<Contact>(_contactRepository.FindAll());
}
}
public void NewContact()
{
OpenContact(new Contact());
}
public void SaveContact(Contact contact)
{
if (!CurrentContacts.Contains(contact))
{
CurrentContacts.Add(contact);
}
_contactRepository.Save(contact);
StatusText = string.Format("Contact '{0}' was saved.", contact.LookupName);
}
public void DeleteContact(Contact contact)
{
if (CurrentContacts.Contains(contact))
{
CurrentContacts.Remove(contact);
}
_contactRepository.Delete(contact);
StatusText = string.Format("Contact '{0}' was deleted.", contact.LookupName);
}
public void CloseTab<T>(PresenterBase<T> presenter)
{
View.RemoveTab(presenter);
}
private void OpenContact(Contact contact)
{
if (contact == null) return;
View.AddTab(new EditContactPresenter(this, new EditContactView(), contact));
}
public void DisplayAllContacts()
{
throw new NotImplementedException();
}
}
}
namespace CMnew.Presenters
{
public class EditContactPresenter : PresenterBase<EditContactView>
{
private readonly ApplicationPresenter _applicationPresenter;
private Contact _contact;
public EditContactPresenter(ApplicationPresenter applicationPresenter, EditContactView view, Contact contact) : base(view, "Contact.LookupName")
{
_applicationPresenter = applicationPresenter;
_contact = contact;
}
public Contact Contact
{
get { return _contact; }
set { _contact = value; }
}
public void SelectImage()
{
string imagePath = View.AskUserForImagePath();
if (!string.IsNullOrEmpty(imagePath))
{
Contact.ImagePath = imagePath;
}
}
public void Save()
{
_applicationPresenter.SaveContact(Contact);
}
public void Delete()
{
_applicationPresenter.CloseTab(this);
_applicationPresenter.DeleteContact(Contact);
}
public void Close()
{
_applicationPresenter.CloseTab(this);
}
public override bool Equals(object obj)
{
EditContactPresenter presenter = obj as EditContactPresenter;
return presenter != null && presenter.Contact.Equals(Contact);
}
}
}
namespace CMnew.Views
{
/// <summary>
/// Interaction logic for EditContactView.xaml
/// </summary>
public partial class EditContactView : UserControl
{
public EditContactView()
{
InitializeComponent();
}
public EditContactPresenter Presenter
{
get { return DataContext as EditContactPresenter; }
}
private void Save_Click(object sender, RoutedEventArgs e)
{
Presenter.Save();
}
private void Delete_Click(object sender, RoutedEventArgs e)
{
Presenter.Delete();
}
private void Close_Click(object sender, RoutedEventArgs e)
{
Presenter.Close();
}
private void SelectImage_Click(object sender, RoutedEventArgs e)
{
Presenter.SelectImage();
}
public string AskUserForImagePath()
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.ShowDialog();
return dlg.FileName;
}
}
}
<UserControl x:Class="CMnew.Views.EditContactView"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CMnew.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<DockPanel Margin="5">
<Border DockPanel.Dock="Top">
<DockPanel LastChildFill="False">
<TextBlock DockPanel.Dock="Left" Text="{Binding Contact.LastName}"/>
<TextBlock DockPanel.Dock="Left" Text=", "/>
<TextBlock DockPanel.Dock="Left" Text="{Binding Contact.FirstName}"/>
<TextBlock DockPanel.Dock="Right" Text="{Binding Contact.Organization}"/>
</DockPanel>
</Border>
<StackPanel DockPanel.Dock="Bottom" Style="{StaticResource buttonPanel}">
<Button Content="Save" Click="Save_Click"/>
<Button Content="Delete" Click="Delete_Click"/>
<Button Content="Close" Click="Close_Click"/>
</StackPanel>
<WrapPanel>
<GroupBox BorderBrush="{StaticResource lightBlueBrush}">
<GroupBox.Header>
<Border Background="{StaticResource lightBlueBrush}" Style="{StaticResource groupBoxHeader}">
<TextBlock Text="General"/>
</Border>
</GroupBox.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="175"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.RowSpan="4">
<Border Background="Gray"
CornerRadius="6"
Margin="2 2 0 0"
Opacity=".5"/>
<Border Margin="2 2 4 4"
Background="White"/>
<Viewbox Margin="2 2 4 4">
<Image Source="{Binding Contact.ImagePath}" />
</Viewbox>
<Border BorderBrush="{StaticResource lightBlueBrush}"
BorderThickness="2"
Background="Transparent"
CornerRadius="6"
Margin="0 0 2 2"/>
<Button Style="{StaticResource openButton}"
Background="White"
Foreground="{StaticResource lightBlueBrush}"
BorderBrush="{StaticResource lightBlueBrush}"
ToolTip="Change Picture"
Click="SelectImage_Click" />
</Grid>
<Label Grid.Column="1"
Content="_First Name:"
Target="{Binding ElementName=firstName}"/>
<TextBox x:Name="firstName"
Grid.Column="2"
Text="{Binding Contact.FirstName}"/>
<Label Grid.Row="1"
Grid.Column="1"
Content="_Last Name:"
Target="{Binding ElementName=lastName}"/>
<TextBox x:Name="lastName"
Grid.Row="1"
Grid.Column="2"
Text="{Binding Contact.LastName}"/>
<Label Grid.Row="2"
Grid.Column="1"
Content="Or_ganization:"
Target="{Binding ElementName=organization}"/>
<TextBox x:Name="organization"
Grid.Row="2"
Grid.Column="2"
Text="{Binding Contact.Organization}"/>
<Label Grid.Row="3"
Grid.Column="1"
Content="_Job Title:"
Target="{Binding ElementName=jobTitle}"/>
<TextBox x:Name="jobTitle"
Grid.Row="3"
Grid.Column="2"
Text="{Binding Contact.JobTitle}"/>
</Grid>
</GroupBox>
<GroupBox BorderBrush="{StaticResource greenBrush}">
<GroupBox.Header>
<Border Background="{StaticResource greenBrush}"
Style="{StaticResource groupBoxHeader}">
<TextBlock Text="Address"/>
</Border>
</GroupBox.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Content="Line _1:"
Target="{Binding ElementName=line1}" />
<TextBox x:Name="line1"
Grid.Column="1"
Grid.ColumnSpan="3"
Text="{Binding Contact.Address.Line1}" />
<Label Grid.Row="1"
Content="Line _2:"
Target="{Binding ElementName=line2}" />
<TextBox x:Name="line2"
Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="3"
Text="{Binding Contact.Address.Line2}" />
<Label Grid.Row="2"
Content="Ci_ty:"
Target="{Binding ElementName=city}" />
<TextBox x:Name="city"
Grid.Row="2"
Grid.Column="1"
Text="{Binding Contact.Address.City}" />
<Label Grid.Row="2"
Grid.Column="2"
Content="_State:"
Target="{Binding ElementName=state}" />
<TextBox x:Name="state"
Grid.Row="2"
Grid.Column="3"
Text="{Binding Contact.Address.State}" />
<Label Grid.Row="3"
Grid.Column="0"
Content="_Zip:"
Target="{Binding ElementName=zip}" />
<TextBox x:Name="zip"
Grid.Row="3"
Grid.Column="1"
Text="{Binding Contact.Address.Zip}" />
<Label Grid.Row="3"
Grid.Column="2"
Content="Countr_y:"
Target="{Binding ElementName=country}" />
<TextBox x:Name="country"
Grid.Row="3"
Grid.Column="3"
Text="{Binding Contact.Address.Country}" />
</Grid>
</GroupBox>
<GroupBox BorderBrush="{StaticResource redBrush}">
<GroupBox.Header>
<Border Background="{StaticResource redBrush}"
Style="{StaticResource groupBoxHeader}">
<TextBlock Text="Phone"/>
</Border>
</GroupBox.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Content="_Office:"
Target="{Binding ElementName=office}"/>
<TextBox x:Name="office"
Grid.Column="1"
Text="{Binding Contact.OfficePhone}" />
<Label Grid.Row="1"
Content="_Cell:"
Target="{Binding ElementName=cell}" />
<TextBox x:Name="cell"
Grid.Row="1"
Grid.Column="1"
Text="{Binding Contact.CellPhone}" />
<Label Grid.Row="2"
Content="_Home:"
Target="{Binding ElementName=home}" />
<TextBox x:Name="home"
Grid.Row="2"
Grid.Column="1"
Text="{Binding Contact.HomePhone}" />
</Grid>
</GroupBox>
<GroupBox BorderBrush="{StaticResource brownBrush}">
<GroupBox.Header>
<Border Background="{StaticResource brownBrush}"
Style="{StaticResource groupBoxHeader}">
<TextBlock Text="Email"/>
</Border>
</GroupBox.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Content="_Primary:"
Target="{Binding ElementName=primaryEmail}"/>
<TextBox x:Name="primaryEmail"
Grid.Column="1"
Text="{Binding Contact.PrimaryEmail}"/>
<Label Grid.Row="1"
Content="S_econdary:"
Target="{Binding ElementName=secondaryEmail}"/>
<TextBox x:Name="secondaryEmail"
Grid.Row="1"
Grid.Column="1"
Text="{Binding Contact.SecondaryEmail}"/>
</Grid>
</GroupBox>
</WrapPanel>
</DockPanel>
</UserControl>
This is likely your problem:
public class Address : EntityBase
{
[ForeignKey("Contact")] // <---
public int AddressId { get; set; }
along with that you are disposing your DbContext after saving. When the EF DbContext encounters a new entity (Contact) with a related entity that you expect exists but isn't tracked, it will treat that entity as a new entity. This behaviour is likely the root of what is tripping you up passing entities around. The Address is no longer tracked so an existing address record is attempted to be re-inserted and since it is the FK pointing at Contact, it will not generate a new value, but try to use the current ID of the contact.
Typically Address would expect to have a PK (AddressID) and a FK (ContactId) if your contact could have multiple addresses. Either that or the Contact entity would have an AddressID. (Where a single address could serve multiple contacts) If you want one contact to have only one address then either put the address fields into Contact itself, or create a ContactAddressDetails table with a ContactId as the PK+FK for a HasRequired/WithRequired relationship if you want it in a separate table. The desired relationship will define what the fields on the entity / schema will look like.
Thanks for your answers but i found the problem:
public ContactRepository()
{
if (this.GetAll() == null)
{
_contactStore = new List<Contact>();
}
else
{
_contactStore = this.GetAll();
}
}
public List<Contact> GetAll() => Context.Contacts.ToList();
In the ContactRepository constructor after calling GetAll() i have to dispose the current context:
private List<Contact> _contactStore = new List<Contact>();
public ContactRepository()
{
_contactStore = this.GetAll();
Context.Dispose();
Context = new CMEntities();
}
and everything is working fine.
i do not know is there a better solution instead of continuous calling Context.Dispose() or it is the right way?

Dynamically filtering a CollectionViewSource bound to a SelectedItem via MVVM

I've been digging back into some projects in WPF, and come across a hurdle that I haven't been able to find a directly related solution for.
Essentially I want to filter a child property of a SelectedItem dynamically (via text entered in the filter box, something along the lines of .Contains(filter)). The UI displays correctly in the sample project, but after attempting to implement solutions from every hit possible on SO or otherwise, I've come up blank, or making serious compromises to the MVVM pattern.
ParentItem:
public class ParentItem
{
public string Name { get; set; }
public List<string> ChildItems { get; set; }
public DateTime CreatedOn { get; set; }
public bool IsActive { get; set; }
public ParentItemStatus Status { get; set; }
}
public enum ParentItemStatus
{
Status_One,
Status_Two
}
ViewModel:
public class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<ParentItem> ParentItems { get; set; }
public MainWindowViewModel()
{
ParentItems = new ObservableCollection<ParentItem>();
LoadDummyParentItems();
}
private ICommand _filterChildrenCommand;
public ICommand FilterChildrenCommand => _filterChildrenCommand ?? (_filterChildrenCommand = new RelayCommand(param => FilterChildren((string)param), param => CanFilterChildren((string)param)));
private bool CanFilterChildren(string filter)
{
//TODO: Check for selected item in real life.
return filter.Length > 0;
}
private void FilterChildren(string filter)
{
//TODO: Filter?
}
private void LoadDummyParentItems()
{
for (var i = 0; i < 20; i++)
{
ParentItems.Add(new ParentItem()
{
Name = $"Parent Item {i}",
CreatedOn = DateTime.Now.AddHours(i),
IsActive = i % 2 == 0 ? true : false,
Status = i % 2 == 0 ? ParentItemStatus.Status_Two : ParentItemStatus.Status_One,
ChildItems = new List<string>() { $"Child one_{i}", $"Child two_{i}", $"Child three_{i}", $"Child four_{i}" }
});
}
}
}
MainWindow:
<Window x:Class="FilteringDemo.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FilteringDemo.Views"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<CollectionViewSource x:Key="ChildItemsViewSource" Source="{Binding ElementName=ItemList, Path=SelectedItem.ChildItems}" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".25*"/>
<ColumnDefinition Width=".75*"/>
</Grid.ColumnDefinitions>
<ListView x:Name="ItemList" Grid.Column="0" Margin="2" ItemsSource="{Binding ParentItems}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ElementName=ItemList, Path=SelectedItem.Name}" Margin="2"/>
<TextBlock Grid.Column="1" Text="{Binding ElementName=ItemList, Path=SelectedItem.CreatedOn}" Margin="2"/>
<TextBlock Grid.Column="2" Text="{Binding ElementName=ItemList, Path=SelectedItem.IsActive}" Margin="2"/>
<TextBlock Grid.Column="3" Text="{Binding ElementName=ItemList, Path=SelectedItem.Status}" Margin="2"/>
</Grid>
<ListView Grid.Row="1" Margin="2" ItemsSource="{Binding Source={StaticResource ChildItemsViewSource}}" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Contains:" Margin="2" VerticalAlignment="Center"/>
<TextBox x:Name="ChildFilterInput" Grid.Column="1" Margin="2" />
<Button Grid.Column="2" Content="Filter" Width="100" Margin="2" Command="{Binding FilterChildrenCommand}" CommandParameter="{Binding ElementName=ChildFilterInput, Path=Text}"/>
</Grid>
</Grid>
</Grid>
</Window>
I've tried various implementations of adding a Filter event handler on the CollectionViewSource but have been unable to make them dynamic. It also seems like most examples/tutorials only deal directly with the parent item or static filters.
In a non-MVVM mindset, I was thinking to have an interaction trigger drive the selected item back into the ViewModel, and then create a filtered ICollectionView which the ChildItems ListView would bind to, but it seems like I can't be the only person trying this, and that there must be an easier MVVM binding friendly way.
The following example shows a simple solution to implement live filtering on a collection:
Person.cs
class Person
{
public Person(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
ViewModel.cs
class ViewModel
{
public ViewModel()
{
this.Persons = new ObservableCollection<Person>()
{
new Person("Derek", "Zoolander"),
new Person("Tony", "Montana"),
new Person("John", "Wick"),
new Person("The", "Dude"),
new Person("James", "Bond"),
new Person("Walter", "White")
};
}
private void FilterData(string filterPredicate)
{
// Execute live filter
CollectionViewSource.GetDefaultView(this.Persons).Filter =
item => (item as Person).FirstName.StartsWith(filterPredicate, StringComparison.OrdinalIgnoreCase);
}
private string searchPredicate;
public string SearchPredicate
{
get => this.searchFilter;
set
{
this.searchPredicate = value;
FilterData(value);
}
}
public ObservableCollection<Person> Persons { get; set; }
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding SearchPredicate, UpdateSourceTrigger=PropertyChanged"} />
<ListView ItemsSource="{Binding Persons}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Firstname" DisplayMemberBinding="{Binding FirstName}" />
<GridViewColumn Header="Lastname" DisplayMemberBinding="{Binding LastName}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Window>
Update
It seems like you are having problems to filter the child items. The following example is more specific to your scenario:
DataItem.cs
class DataItem
{
public DataItem(string Name)
{
this.Name = name;
}
public string Name { get; set; }
public ObservableCollection<DataItem> ChildItems { get; set; }
}
ViewModel.cs
class ViewModel
{
public ViewModel()
{
this.ParentItems = new ObservableCollection<DataItem>()
{
new DataItem("Ben Stiller") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("Zoolander"), new DataItem("Tropical Thunder") }},
new DataItem("Al Pacino") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("Scarface"), new DataItem("The Irishman") }},
new DataItem("Keanu Reeves") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("John Wick"), new DataItem("Matrix") }},
new DataItem("Bryan Cranston") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("Breaking Bad"), new DataItem("Malcolm in the Middle") }}
};
}
private void FilterData(string filterPredicate)
{
// Execute live filter
CollectionViewSource.GetDefaultView(this.SelectedParentItem.ChildItems).Filter =
item => (item as DataItem).Name.StartsWith(filterPredicate, StringComparison.OrdinalIgnoreCase);
}
private string searchPredicate;
public string SearchPredicate
{
get => this.searchFilter;
set
{
this.searchPredicate = value;
FilterData(value);
}
}
public ObservableCollection<DataItem> ParentItems { get; set; }
public DataItem SelectedParentItem { get; set; }
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<StackPanel>
<ListView ItemsSource="{Binding ParentItems}"
SelectedItem="{Binding SelectedParentItem}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBox Text="{Binding SearchPredicate, UpdateSourceTrigger=PropertyChanged}" />
<ListView ItemsSource="{Binding SelectedParentItem.ChildItems}" />
</StackPanel>
</Window>
Using the examples from #BionicCode- I added a SelectedParentItem INPC property to the ViewModel, performed the filtering on that via CollectionViewSource.Filter, and bound the ChildItems ListView to SelectedParentItem.ChildItems.
I did not bind the text box property changed to a backing field in the VM as per #BionicCode's example, as the "real" ChildItems might be in the mid 10,000's and I didn't want it to filter on each keystroke. So this answer implements the filter button and text box command, and the CanFilterChildren is doing a proper null check.
MainWindowViewModel.cs:
public class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<ParentItem> ParentItems { get; set; }
private ParentItem _selectedParentItem;
public ParentItem SelectedParentItem
{
get { return _selectedParentItem; }
set { SetProperty(ref _selectedParentItem, value); }
}
public MainWindowViewModel()
{
ParentItems = new ObservableCollection<ParentItem>();
LoadDummyParentItems();
}
private ICommand _filterChildrenCommand;
public ICommand FilterChildrenCommand => _filterChildrenCommand ?? (_filterChildrenCommand = new RelayCommand(param => FilterChildren((string)param), param => CanFilterChildren((string)param)));
private bool CanFilterChildren(string filter) => SelectedParentItem != null && filter.Length > 0;
private void FilterChildren(string filter)
{
CollectionViewSource.GetDefaultView(SelectedParentItem.ChildItems).Filter = item => (item as string).Contains(filter);
}
private void LoadDummyParentItems()
{
for (var i = 0; i < 20; i++)
{
ParentItems.Add(new ParentItem()
{
Name = $"Parent Item {i}",
CreatedOn = DateTime.Now.AddHours(i),
IsActive = i % 2 == 0 ? true : false,
Status = i % 2 == 0 ? ParentItemStatus.Status_Two : ParentItemStatus.Status_One,
ChildItems = new List<string>() { $"Child one_{i}", $"Child two_{i}", $"Child three_{i}", $"Child four_{i}" }
});
}
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainWindowViewModel();
this.DataContext = _viewModel;
}
}
MainWindow.xaml:
<Window x:Class="FilteringDemo.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FilteringDemo.Views"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".25*"/>
<ColumnDefinition Width=".75*"/>
</Grid.ColumnDefinitions>
<ListView x:Name="ItemList" Grid.Column="0" Margin="2" ItemsSource="{Binding ParentItems}" SelectedItem="{Binding SelectedParentItem, Mode=OneWayToSource}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ElementName=ItemList, Path=SelectedItem.Name}" Margin="2"/>
<TextBlock Grid.Column="1" Text="{Binding ElementName=ItemList, Path=SelectedItem.CreatedOn}" Margin="2"/>
<TextBlock Grid.Column="2" Text="{Binding ElementName=ItemList, Path=SelectedItem.IsActive}" Margin="2"/>
<TextBlock Grid.Column="3" Text="{Binding ElementName=ItemList, Path=SelectedItem.Status}" Margin="2"/>
</Grid>
<ListView Grid.Row="1" Margin="2" ItemsSource="{Binding SelectedParentItem.ChildItems}" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="ChildFilterInput" Grid.Column="0" Margin="2">
<TextBox.InputBindings>
<KeyBinding Command="{Binding FilterChildrenCommand}" CommandParameter="{Binding ElementName=ChildFilterInput, Path=Text}" Key="Return" />
</TextBox.InputBindings>
</TextBox>
<Button Grid.Column="1" Content="Filter" Width="100" Margin="2" Command="{Binding FilterChildrenCommand}" CommandParameter="{Binding ElementName=ChildFilterInput, Path=Text}"/>
</Grid>
</Grid>
</Grid>
</Window>
ViewModelBase.cs:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}
RelayCommand.cs:
public class RelayCommand : ICommand
{
private Predicate<object> _canExecuteMethod;
private Action<object> _executeMethod;
public RelayCommand(Action<object> executeMethod, Predicate<object> canExecuteMethod = null)
{
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecuteMethod == null ? true : _canExecuteMethod(parameter);
}
public void Execute(object parameter)
{
_executeMethod(parameter);
}
}
ParentItem.cs:
public class ParentItem
{
public string Name { get; set; }
public List<string> ChildItems { get; set; }
public DateTime CreatedOn { get; set; }
public bool IsActive { get; set; }
public ParentItemStatus Status { get; set; }
}
public enum ParentItemStatus
{
Status_One,
Status_Two
}
App.xaml:
<Application x:Class="FilteringDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FilteringDemo"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
Note: The MainWindow.xaml file was moved into a "Views" folder, so I'm including the App.xaml with the updated StartupUri in case anyone is trying to copy and paste.

Having problems binding my datagrid to data from my database

i've a problem to use datagrid in wpf mvvm project
Here is my xaml :
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
x:Class="noteManager.MainWindow"
xmlns:vm="clr-namespace:noteManager.ViewModel"
DataContext="{StaticResource noteManagerViewModel}"
Title="NoteManager" Height="490" Width="525">
<Grid Margin="0,0,0,-132.5">
<Grid.RowDefinitions>
<RowDefinition Height="10"></RowDefinition>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="200"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="110"></RowDefinition>
<RowDefinition Height="111"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"></ColumnDefinition>
<ColumnDefinition Width="80"></ColumnDefinition>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="80"></ColumnDefinition>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition Width="50"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Login :" FontSize="16" Grid.Column="2" Margin="51,9,50,0" Grid.RowSpan="2" Height="23" VerticalAlignment="Top" Grid.ColumnSpan="2"/>
<TextBox Text="{Binding Login}" Grid.Row="1" Grid.Column="3" Margin="14,0,86,29" Grid.ColumnSpan="2"/>
<Button Background="LightGreen" Foreground="Green" Command="{Binding testConnexion}" x:Name="testConnexion" Content="Connexion" Grid.Row="1" Grid.Column="2" Margin="51,29,86,0" Grid.ColumnSpan="3"/>
<Button Command="{Binding addUser}" Content="+" Grid.Row="1" Grid.Column="4" Margin="34,1,20,0" RenderTransformOrigin="0.742,0.468"/>
<DataGrid Name="dataGrid1" Grid.Row="2" Margin="8,7,-22,7" AutoGenerateColumns="False"
ItemsSource="{Binding _DataGridNotes}" SelectedItem="{Binding Path=MySelectedNote}" HorizontalAlignment="Center"
Width="480" Grid.ColumnSpan="6" Grid.Column="1">
<DataGrid.Columns>
<DataGridTextColumn Width="100" Binding="{Binding Path=NoteTitle}" Header="Titre" />
<DataGridTextColumn Width="200" Binding="{Binding Path=NoteContent}" Header="Note" />
<DataGridTextColumn Width="100" Binding="{Binding Path=NoteCreatedAt}" Header="Date de création" />
<DataGridTextColumn Width="100" Binding="{Binding Path=NoteUpdatedAt}" Header="Dat MAJ" />
</DataGrid.Columns>
</DataGrid>
<TextBlock Text="Titre" FontSize="16" Grid.Row="3" Grid.Column="1" Margin="27,8,7,1"/>
<TextBox Text="{Binding Path=titre, Mode=TwoWay}" Grid.Row="3" Grid.Column="2" Margin="17,10,23,10" Grid.ColumnSpan="5"/>
<TextBlock Text="Note" FontSize="16" Grid.Row="4" Grid.Column="1" Margin="27,4,7,0"/>
<TextBox Text="{Binding Path=description, Mode=TwoWay}" Grid.Row="4" Grid.Column="2" Margin="17,10,23,8" Grid.ColumnSpan="5"/>
<Button Command="{Binding Path=DeleteNote}" Background="LightPink" Foreground="red" Content="Supprimer" Grid.Row="5" Grid.Column="1" Margin="55,7,26,81" Grid.ColumnSpan="2"/>
<Button Command="{Binding Path=UpdateANote}" Content="Mettre à jour" Grid.Row="5" Grid.Column="3" Margin="14,7,67,81" Grid.ColumnSpan="2" RenderTransformOrigin="0.5,0.5"/>
<Button Command="{Binding Path=AddNote}" Content="Ajouter" Grid.Row="5" Grid.Column="4" Margin="78,7,10,81" Grid.ColumnSpan="3"/>
</Grid>
</Window>
Here is my viewModel :
namespace noteManager.ViewModel
{
public class noteManagerViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void Notify(string property)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
string login;
int currentUser;
public string Login
{
get
{
return login;
}
set
{
login = value; Notify("Login");
}
}
private bool _canExecute;
public noteManagerViewModel()
{
_canExecute = true;
}
private ICommand _testConnexion;
public ICommand testConnexion
{
get
{
return _testConnexion ?? (_testConnexion = new CommandHandler(() => Connexion(), _canExecute));
}
}
private ICommand _addUser;
public ICommand addUser
{
get
{
return _addUser ?? (_addUser = new CommandHandler(() => AjoutUser(), _canExecute));
}
}
private ObservableCollection<DataGridNotes> _DataGridNotes = new ObservableCollection<DataGridNotes>();
public ObservableCollection<DataGridNotes> dataGridNotes
{
// No need for a public setter
get { return _DataGridNotes; }
}
}
the other class that i use :
public class User
{
/*public User()
{
this.Note = new HashSet<Note>();
}*/
public int Id { get; set; }
public string Login { get; set; }
//public virtual ICollection<Note> Note { get; set; }
}
public class Note : INotifyPropertyChanged
{
public int Id { get; set; }
public string NoteText { get; set; }
public string ContentText { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public int UserId { get; set; }
//public virtual User User { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void Notify(string property)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class DataGridNotes
{
private string _noteTitle;
private string _noteContent;
private string _noteCreatedAt;
private string _noteUpdatedAt;
public string NoteTitle { get { return _noteTitle; } set { _noteTitle = value; } }
public string NoteContent { get { return _noteContent; } set { _noteContent = value; } }
public string NoteCreatedAt { get { return _noteCreatedAt; } set { _noteCreatedAt = value; } }
public string NoteUpdatedAt { get { return _noteUpdatedAt; } set { _noteUpdatedAt = value; } }
}
sorry for the ugly code, new to c# for a project.
i want to use the datagrid in my viewmodel but don't find a way to make it work (would like to write data from mysql database in the datagrid
Have you an idea to make it work ?
thx in advance
Ok, It's tough to spot what you are doing wrong without seeing the ViewModel, however you may want to check the following:
1) The DataContext is correct.
2) The property _DataGridNotes exists. Check the program output to make sure that there are no warnings informing you that bindings are broken.
The property you are looking to have should look something like this:
List<Note> _DataGridNotes
{
get
{
// get notes from SQL request
// construct list of Note and return list
}
}
You should also make sure that the Note class contains the properties required (NoteTitle, NoteContent, NoteCreatedAt, NoteUpdatedAt).
It might also be worth passing back some dummy notes to debug if the problem lies in the request to the SQL database.
The problem is that you are trying to bind to a private Observable collection _DataGridNotes where you should be binding to the property dataGridNotes:
ItemsSource="{Binding dataGridNotes}"

Windows Phone - Binding View to View Model

So, I am on my way learning MVVM Pattern for Windows Phone, and stuck how to bind the View to my ViewModel. App that I build now is getting current and next 5 days weather and display it to one of my panorama item on MainPage.xaml using UserControl.
I cannot just simply set the Forecasts.ItemsSource = forecast; in my WeatherViewModel, it says that Forecasts (Listbox element name in WeatherView) not exist in the current context.
Can anybody teach me how to bind it? and anybody have a good source/example sample to mvvm pattern in windows-phone? Thanks before.
EDIT:
WeatherModel.cs
namespace JendelaBogor.Models
{
public class WeatherModel
{
public string Date { get; set; }
public string ObservationTime { get; set; }
public string WeatherIconURL { get; set; }
public string Temperature { get; set; }
public string TempMaxC { get; set; }
public string TempMinC { get; set; }
public string Humidity { get; set; }
public string WindSpeedKmph { get; set; }
}
}
WeatherViewModel.cs
namespace JendelaBogor.ViewModels
{
public class WeatherViewModel : ViewModelBase
{
private string weatherURL = "http://free.worldweatheronline.com/feed/weather.ashx?q=";
private const string City = "Bogor,Indonesia";
private const string APIKey = "APIKEY";
private IList<WeatherModel> _forecasts;
public IList<WeatherModel> Forecasts
{
get
{
if (_forecasts == null)
{
_forecasts = new List<WeatherModel>();
}
return _forecasts;
}
private set
{
_forecasts = value;
if (value != _forecasts)
{
_forecasts = value;
this.NotifyPropertyChanged("Forecasts");
}
}
}
public WeatherViewModel()
{
WebClient downloader = new WebClient();
Uri uri = new Uri(weatherURL + City + "&num_of_days=5&extra=localObsTime&format=xml&key=" + APIKey, UriKind.Absolute);
downloader.DownloadStringCompleted += new DownloadStringCompletedEventHandler(ForecastDownloaded);
downloader.DownloadStringAsync(uri);
}
private void ForecastDownloaded(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Result == null || e.Error != null)
{
MessageBox.Show("Cannot load Weather Forecast!");
}
else
{
XDocument document = XDocument.Parse(e.Result);
var current = from query in document.Descendants("current_condition")
select new WeatherModel
{
ObservationTime = DateTime.Parse((string)query.Element("localObsDateTime")).ToString("HH:mm tt"),
Temperature = (string)query.Element("temp_C"),
WeatherIconURL = (string)query.Element("weatherIconUrl"),
Humidity = (string)query.Element("humidity"),
WindSpeedKmph = (string)query.Element("windspeedKmph")
};
this.Forecasts = (from query in document.Descendants("weather")
select new WeatherModel
{
Date = DateTime.Parse((string)query.Element("date")).ToString("dddd"),
TempMaxC = (string)query.Element("tempMaxC"),
TempMinC = (string)query.Element("tempMinC"),
WeatherIconURL = (string)query.Element("weatherIconUrl")
}).ToList();
}
}
}
}
WeatherView.xaml
<UserControl x:Class="JendelaBogor.Views.WeatherView"
xmlns:vm="clr-namespace:JendelaBogor.ViewModels">
<UserControl.DataContext>
<vm:WeatherViewModel />
</UserControl.DataContext>
<Grid Margin="0,-10,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="Current" Grid.Row="0" Height="150" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" delay:LowProfileImageLoader.UriSource="{Binding WeatherIconURL}" Width="120" Height="120" VerticalAlignment="Top"/>
<StackPanel Grid.Column="1" Height="200" VerticalAlignment="Top">
<TextBlock Text="{Binding Temperature}" FontSize="22"/>
<TextBlock Text="{Binding ObservationTime}" FontSize="22"/>
<TextBlock Text="{Binding Humidity}" FontSize="22"/>
<TextBlock Text="{Binding Windspeed}" FontSize="22"/>
</StackPanel>
</Grid>
<Grid Grid.Row="1" Height="300" VerticalAlignment="Bottom" Margin="10,0,0,0">
<StackPanel VerticalAlignment="Top">
<StackPanel Height="40" Orientation="Horizontal" Margin="0,0,0,0">
<TextBlock Text="Date" FontSize="22" Width="170"/>
<TextBlock Text="FC" FontSize="22" Width="60"/>
<TextBlock Text="Max" TextAlignment="Right" FontSize="22" Width="90"/>
<TextBlock Text="Min" TextAlignment="Right" FontSize="22" Width="90"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ListBox ItemsSource="{Binding Forecasts}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Height="40" Orientation="Horizontal" Margin="0,10,0,0">
<TextBlock Text="{Binding Date}" FontSize="22" TextAlignment="Left" Width="170" />
<Image delay:LowProfileImageLoader.UriSource="{Binding WeatherIconURL}" Width="40" Height="40" />
<TextBlock Text="{Binding TempMaxC, StringFormat='\{0\} °C'}" TextAlignment="Right" FontSize="22" Width="90" />
<TextBlock Text="{Binding TempMinC, StringFormat='\{0\} °C'}" TextAlignment="Right" FontSize="22" Width="90" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
</Grid>
</Grid>
</UserControl>
MainPage.xaml
<controls:PanoramaItem x:Name="Weather" Header="weather">
<views:WeatherView />
</controls:PanoramaItem>
You need to tell the view what viewmodel you are using. By adding
<UserControl
xmlns:vm="clr-namespace:JendelaBogor.ViewModels">
<UserControl.DataContext>
<vm:WeatherViewModel />
</UserControl.DataContext>
</UserControl>
all {Binding}'s are mapped to the class WeatherViewModel. By using the ItemsSource property on the listbox as Reed suggests you can then bind all items from a list that you expose through a property.
If the list is ever changed while running the application, consider using an ObservableCollection and clearing it and adding all new items when new data is received. If you do, your GUI will simply update with it.
The ViewModel doesn't know about the view.
You need to make a Forecasts property on the ViewModel, and bind the ItemsSource to it from your View. In your view, change the ListBox to:
<!-- No need for a name - just add the binding -->
<ListBox ItemsSource="{Binding Forecasts}">
Then, in your ViewModel, add:
// Add a backing field
private IList<WeatherModel> forecasts;
// Add a property implementing INPC
public IList<WeatherModel> Forecasts
{
get { return forecasts; }
private set
{
forecasts = value;
this.RaisePropertyChanged("Forecasts");
}
}
You can then set this in your method:
this.Forecasts = (from query in document.Descendants("weather")
select new WeatherModel
{
Date = DateTime.Parse((string)query.Element("date")).ToString("dddd"),
TempMaxC = (string)query.Element("tempMaxC"),
TempMinC = (string)query.Element("tempMinC"),
WeatherIconURL = (string)query.Element("weatherIconUrl")
})
.ToList(); // Turn this into a List<T>

Failing To Bind Dynamically Generated Buttons & Textboxes in WPF

I was working on dynamic generation of labels, buttons and Textbox in my WPF application. Well I was successful in dynamically creating them but I am facing one major issue in it.
Xaml:
<ListBox x:Name="myViewChannelList" HorizontalAlignment="Stretch" Height="Auto" ItemsSource="{Binding}" Margin="0" VerticalAlignment="Stretch" Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=ChanelName}" Margin="50,20,0,0"></Label>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=VoltageText}" Height="25" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />
<Button Grid.Column="1" Content="Set" Height="25" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" ></Button>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Model Class:
private string _ChanelName = "";
public String ChanelName
{
get
{
return _ChanelName;
}
set
{
if (value != _ChanelName)
{
_ChanelName = value;
OnPropertyChanged("ChanelName");
}
}
}
// Constructor
public VoltageModel(string ChanelName)
{
this.ChanelName = ChanelName;
}
public override string ToString()
{
return _ChanelName;
}
ViewModel Class:
class ChannelList : ObservableCollection<VoltageModel>, INotifyPropertyChanged
{
private string _VoltageText;
public string VoltageText
{
get { return _VoltageText; }
set
{
_VoltageText = value;
OnPropertyChanged("VoltageText");
}
}
// Method gets called when Set Button Is Clicked
public void SetCommandExecuted()
{
string val = VoltageText;
}
//Notify Property Changed members are present
}
Xaml.cs Class:
ChannelList myChanels = new ChannelList();
public VoltageView() // Constructor
{
InitializeComponent();
myChanels.Add(new VoltageModel("VDD__Main"));
myChanels.Add(new VoltageModel("VDD__IO__AUD"));
myChanels.Add(new VoltageModel("VDD__CODEC__AUD"));
myViewChannelList.DataContext = myChanels;
}
This gives me 3 Labels(Content as above), 3 textboxes and 3 buttons when I run the application.
Now when I enter the value inside the textbox it shows null on button click when I put a breakpoint in SetCommandExecuted(). Most importantly any of the 4 button I click generates the event. I want the first textbox and first button to be in sync(bind), 2nd textbx and 2nd button to be in sync and so on. Basically each control must be in sync with the other control in a row. It should not effect the other rows. Is it possible???
Here is the solution to your question. As general practice you want to avoid all logic, building your data, etc. in the code behind. All the business logic should be in the view model which will make it easier to unit test.
Here is the view
.xaml
<StackPanel>
<ListBox HorizontalAlignment="Stretch"
Height="Auto"
ItemsSource="{Binding VoltageCollection}"
VerticalAlignment="Stretch"
Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Width="100"
Content="{Binding ChannelName}" />
<TextBox Width="100"
Text="{Binding VoltageText}" />
<Button Margin="10,0,0,0"
Content="Set"
Command="{Binding VoltageCommand}"
CommandParameter="{Binding VoltageText}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Here is the code behind
.xaml.cs
private ChannelListViewModel m_voltageViewModel;
public MainWindow()
{
InitializeComponent();
m_voltageViewModel = new ChannelListViewModel();
m_voltageViewModel.Initialize();
DataContext = m_voltageViewModel;
}
Here is the Model: VoltageModel
public class VoltageModel : INotifyPropertyChanged
{
public string ChannelName { get; set; }
private string m_voltageText;
public string VoltageText
{
get { return m_voltageText; }
set
{
m_voltageText = value;
OnPropertyChanged("VoltageText");
}
}
public ICommand VoltageCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is the ViewModel: ChannelListViewModel
public class ChannelListViewModel
{
private ICommand m_voltageCommand;
public ChannelListViewModel()
{
m_voltageCommand = new DelegateCommand(x => SetCommandExecute(x));
}
public void Initialize()
{
VoltageCollection = new ObservableCollection<VoltageModel> { new VoltageModel() { ChannelName = "VDD__Main", VoltageText = String.Empty, VoltageCommand = m_voltageCommand },
new VoltageModel() { ChannelName = "VDD__IO__AUD", VoltageText = String.Empty, VoltageCommand = m_voltageCommand },
new VoltageModel() { ChannelName = "VDD__CODEC__AUD", VoltageText = String.Empty, VoltageCommand = m_voltageCommand }};
}
public ObservableCollection<VoltageModel> VoltageCollection { get; set; }
public void SetCommandExecute(object voltageText)
{
Debug.WriteLine(voltageText);
}
}
Finally simple DelegateCommand class DelegateCommand
public class DelegateCommand : ICommand
{
Action<object> m_executeDelegate;
public DelegateCommand(Action<object> executeDelegate)
{
m_executeDelegate = executeDelegate;
}
public void Execute(object parameter)
{
m_executeDelegate(parameter);
}
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
i didn't get much into what was wrong since i recognized 2 things that were generally very wrong ,and they might be the problem for unexpected behavior on your part .
the first : your DataTemplate places your control one on top of the other .
fix :
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=ChanelName}" />
<TextBox Grid.Column="1" Text="{Binding Path=VoltageText}" />
<Button Grid.Column="2" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" />
</Grid>
</DataTemplate>
the second : your Properties are set after PropertyChanged event was risen so they would not be updated until the next time you input a value.
fix :
private T _property;
public T Property
{
get { return _property; }
set
{
_property = value;
OnPropertyChanged("Property");
}
}
make these fixes and edit your post if you still have issues post a comment under my answer.

Categories