WPF Accessing subitems in a ListView/GridView - wpf

I'm attempting to access a listview subitem. I have to insert columns into this ListView/GridView dynamically as this program accesses multiple databases with different value types. If need be I can portion out the database searching into multiple tabs, but for ease of use, I'd rather not take this route.
I've been searching for roughly 3-4 days for a solution. This class organizes the subitems data from a OleDB query. Here is a snippet of the code I'm using:
public class Repair
{
public string RP { get; set; }
public string SN { get; set; }
public DateTime REC { get; set; }
public DateTime START { get; set; }
public string CUST { get; set; }
public string SP { get; set; }
public string TECH { get; set; }
public string STATUS { get; set; }
public string MODEL { get; set; }
public string NOTES { get; set; }
public DateTime ACCUSED { get; set; }
public string ACCNOTES { get; set; }
public int ID { get; set; }
}
public IList<Repair> OpenRepair { get; set; }
It then inserts the data into the ListView with the following snippet:
var gridView = new GridView();
this.searchListView.View = gridView;
gridView.Columns.Add(new GridViewColumn { Header = "RMA #", DisplayMemberBinding = new System.Windows.Data.Binding("RP") });
gridView.Columns.Add(new GridViewColumn { Header = "Serial", DisplayMemberBinding = new System.Windows.Data.Binding("SN") });
gridView.Columns.Add(new GridViewColumn { Header = "Recieved", DisplayMemberBinding = new System.Windows.Data.Binding("REC") });
gridView.Columns.Add(new GridViewColumn { Header = "Start", DisplayMemberBinding = new System.Windows.Data.Binding("START") });
gridView.Columns.Add(new GridViewColumn { Header = "Customer", DisplayMemberBinding = new System.Windows.Data.Binding("CUST") });
gridView.Columns.Add(new GridViewColumn { Header = "Sales Person", DisplayMemberBinding = new System.Windows.Data.Binding("SP") });
gridView.Columns.Add(new GridViewColumn { Header = "Technician", DisplayMemberBinding = new System.Windows.Data.Binding("TECH") });
gridView.Columns.Add(new GridViewColumn { Header = "Status", DisplayMemberBinding = new System.Windows.Data.Binding("STATUS") });
gridView.Columns.Add(new GridViewColumn { Header = "Repair Notes", DisplayMemberBinding = new System.Windows.Data.Binding("NOTES") });
gridView.Columns.Add(new GridViewColumn { Header = "Accidental Used Date", DisplayMemberBinding = new System.Windows.Data.Binding("ACCUSED") });
gridView.Columns.Add(new GridViewColumn { Header = "Accidental Notes", DisplayMemberBinding = new System.Windows.Data.Binding("ACCNOTES") });
gridView.Columns.Add(new GridViewColumn { Header = "ID", DisplayMemberBinding = new System.Windows.Data.Binding("ID"), Width = 0 });
using (OleDbConnection cn = new OleDbConnection(#"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=X:\***"))
{
cmd.Connection = cn;
cmd.CommandText = sqlstatement;
try
{
cn.Open();
dr = cmd.ExecuteReader();
if (dr.HasRows)
{
while (dr.Read())
{
OpenRepair = new List<Repair>();
if (dr[10].ToString() == "True")
{
OpenRepair.Add(
new Repair()
{
RP = dr[0].ToString(),
SN = dr[1].ToString(),
REC = Convert.ToDateTime(dr[2].ToString()).Date,
START = Convert.ToDateTime(dr[3].ToString()).Date,
CUST = dr[4].ToString(),
SP = dr[5].ToString(),
TECH = dr[6].ToString(),
STATUS = dr[7].ToString(),
MODEL = dr[8].ToString(),
NOTES = dr[9].ToString(),
ACCUSED = Convert.ToDateTime(dr[11].ToString()),
ACCNOTES = dr[12].ToString(),
ID = Convert.ToInt32(dr[13].ToString())
});
}
else
{
OpenRepair.Add(
new Repair()
{
RP = dr[0].ToString(),
SN = dr[1].ToString(),
REC = Convert.ToDateTime(dr[2].ToString()).Date,
START = Convert.ToDateTime(dr[3].ToString()).Date,
CUST = dr[4].ToString(),
SP = dr[5].ToString(),
TECH = dr[6].ToString(),
STATUS = dr[7].ToString(),
MODEL = dr[8].ToString(),
NOTES = dr[9].ToString(),
ID = Convert.ToInt32(dr[13].ToString())
});
}
searchListView.Items.Add(OpenRepair);
}
The XAML behind the ListView:
<ListView ItemsSource="{Binding Repair}" SelectionMode="Single" x:Name="searchListView" Margin="0,63,0,0" Background="DarkGray" MouseDoubleClick="searchListView_MouseDoubleClick">
<ListView.View>
<GridView />
</ListView.View>
</ListView>
I've attempted to grab the Subitem values within the ListView/GridView with a few approaches. It has not been successful on either of these codes:
Repair lvi = (Repair)searchListView.SelectedItems[0];
System.Windows.MessageBox.Show(lvi.RP + " " + lvi.SN + " " + lvi.SP);
&
Repair lvi = (Repair)this.customersListView.SelectedItem;
MessageBox.Show(string.Format("Repair: {1}{0}Serial Number:{2}", Environment.NewLine, lvi.RP, lvi.SN));
Even attempted a Windows Form approach, which obviously did not work.
Could someone please at least point me in the correct direction? The error message that I've been obtaining makes sense being it cannot be cast from the object Generic List to class Repair, however, since I am fairly new to WPF, I'm stumped on how to get passed this message!

I tested your code and it works for me, so you probably have got some mistake in the part that you didn't show. But let me try to win you over for using the MVVM pattern, it will make your life so much easier in the long run...
The ViewModel and your Repair class (swapped DateTime for string):
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1.Models
{
public class ListViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<Repair> Repairs { get; private set; }
public ActionCommand DoubleClickCommand { get; private set; }
private Repair _selectedRepair;
public Repair SelectedRepair { get { return _selectedRepair; } set { _selectedRepair = value; OnPropertyChanged("SelectedRepair"); } }
public ListViewModel()
{
this.Repairs = new ObservableCollection<Repair>();
this.Repairs.Add(new Repair { RP = "1000", SN = "A", START = DateTime.Today.ToString("d"), CUST = "C", ID = 0 });
this.Repairs.Add(new Repair { RP = "2000", SN = "D", REC = DateTime.Today.AddDays(-2).ToString("d"), CUST = "E", ID = 1 });
this.DoubleClickCommand = new ActionCommand(DoubleClick);
}
private void DoubleClick()
{
// do whatever (probably not show a MessageBox..)
MessageBox.Show(string.Format("Repair: {1}{0}Serial Number:{2}", Environment.NewLine, this.SelectedRepair.RP, this.SelectedRepair.SN));
}
}
public class ActionCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action _action;
public ActionCommand(Action action) { _action = action; }
public bool CanExecute(object parameter) { return true; }
public void Execute(object parameter) { if (_action != null) _action(); }
}
public class Repair
{
public string RP { get; set; }
public string SN { get; set; }
public string REC { get; set; }
public string START { get; set; }
public string CUST { get; set; }
public string SP { get; set; }
public string TECH { get; set; }
public string STATUS { get; set; }
public string MODEL { get; set; }
public string NOTES { get; set; }
public string ACCUSED { get; set; }
public string ACCNOTES { get; set; }
public int ID { get; set; }
}
}
The View:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:models="clr-namespace:WpfApplication1.Models"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="350" Width="500">
<Window.DataContext>
<models:ListViewModel />
</Window.DataContext>
<ListView ItemsSource="{Binding Repairs}" SelectedItem="{Binding SelectedRepair}" SelectionMode="Single" Margin="0,63,0,0" Background="DarkGray">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DoubleClickCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn Header="RMA #" DisplayMemberBinding="{Binding RP}" />
<GridViewColumn Header="Serial" DisplayMemberBinding="{Binding SN}"/>
<GridViewColumn Header="Recieved" DisplayMemberBinding="{Binding REC}"/>
<GridViewColumn Header="Start" DisplayMemberBinding="{Binding START}"/>
<GridViewColumn Header="Customer" DisplayMemberBinding="{Binding CUST}"/>
<GridViewColumn Header="Sales Person" DisplayMemberBinding="{Binding SP}"/>
<GridViewColumn Header="Technician" DisplayMemberBinding="{Binding TECH}"/>
<GridViewColumn Header="Status" DisplayMemberBinding="{Binding STATUS}"/>
<GridViewColumn Header="Repair Notes" DisplayMemberBinding="{Binding NOTES}"/>
<GridViewColumn Header="Accidental Used Date" DisplayMemberBinding="{Binding ACCUSED}"/>
<GridViewColumn Header="Accidental Notes" DisplayMemberBinding="{Binding ACCNOTES}"/>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" Width="0" />
</GridView>
</ListView.View>
</ListView>
</Window>

Following my comment, here is a more complete answer, for future reference.
The selected object (the one displayed by the SelectedItem) is available through SelectedValue.
And as you use List then you should do the following:
((List<Repair>)searchListView.SelectedValue)[0].SN

Related

WPF: This type of CollectionView does not support changes error

So I have WPF application and I want to add Logger form.
So I create another form:
public partial class LoggerForm : MetroWindow
{
public LoggerForm()
{
InitializeComponent();
DataContext = LogHelper.LogEntries;
lvLogger.ItemsSource = LogHelper.LogEntries;
}
public void AddRandomEntry(LogEntry logEntry)
{
Dispatcher.BeginInvoke((Action)(() => LogHelper.LogEntries.Add(logEntry)));
}
}
XAML ListView:
<ListView Name="lvLogger"
Background="#181818"
Margin="0,0,0,0">
<ListView.View>
<GridView>
<GridViewColumn Width="Auto" Header="Time" DisplayMemberBinding="{Binding DateTime}"/>
<GridViewColumn Width="Auto" Header="Index" DisplayMemberBinding="{Binding Index}"/>
<GridViewColumn Width="Auto" Header="Level" DisplayMemberBinding="{Binding Level}"/>
<GridViewColumn Width="Auto" Header="Source" DisplayMemberBinding="{Binding Source}"/>
<GridViewColumn Width="Auto" Header="Message" DisplayMemberBinding="{Binding Message}"/>
</GridView>
</ListView.View>
</ListView>
Log object:
public class LogEntry
{
public string DateTime { get; set; }
public int Index { get; set; }
public string Source{ get; set; }
public Level Level { get; set; }
public string Message { get; set; }
}
List:
public class LogHelper
{
public static ObservableCollection<LogEntry> LogEntries { get; set; }
}
So this is how I add log into my List:
Open my Logger form:
Thread newWindowThread = new Thread(new ThreadStart(() =>
{
// Create and show the Window
loggerForm = new LoggerForm();
loggerForm.Show();
// Start the Dispatcher Processing
Dispatcher.Run();
}));
// Set the apartment state.
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
newWindowThread.Start();
And from my main form I create LogEntry object and I add it:
private void AddLog(Level level, string message)
{
if (loggerForm != null)
{
LogEntry logEntry = new LogEntry()
{
DateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff"),
Index = LogHelper.LogEntries.Count,
Level = level,
Source = "bla bla",
Message = message
};
loggerForm.AddRandomEntry(logEntry);
}
}
So this works fine until I close the Logger form and open it again. At this point, if I add another LogEntry, I have this error in AddRandomEntry method:
System.NotSupportedException: 'This type of CollectionView does not
support changes to its SourceCollection from a thread different from
the Dispatcher thread.'

How do you create a table for user-input in WPF?

I'm quite new to WPF and XAML and have been learning as I go.
I am writing a program in which the user needs to be able to enter data into a table, with pre-defined columns, which is then later sent to a database. I am, however, unable to make a DataGrid that allowes for this behaviour -
I'm using a - XAML is as follows:
<Grid MinHeight="100" MinWidth="600">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="1"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True">
<DataGrid SelectionMode="Single"
SelectionUnit="Cell"
CanUserAddRows="True"
CanUserDeleteRows="True"
IsReadOnly="False"
AutoGenerateColumns="False"
ItemsSource="{Binding SimpleCollection}">
<DataGrid.Columns>
<DataGridTextColumn Header="Rownummer" Binding="{Binding RowNumber}"/>
<DataGridTextColumn Header="Navn på felt" Binding="{Binding FieldName}"/>
<DataGridTextColumn Header="Forretningsmæssig nøgle" Binding="{Binding BusinessKey}"/>
<DataGridTextColumn Header="Er der datoopl." Binding="{Binding ContainsTimestamps}"/>
<DataGridTextColumn Header="Er der koder, der oversættes via BAS" Binding="{Binding ContainsBASTranslatedCodes}"/>
<DataGridTextColumn Header="Metadata" Binding="{Binding Metadata}"/>
<DataGridTextColumn Header="Evt. bemærkninger" Binding="{Binding AdditionalComments}"/>
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
</Grid>
<Grid>
The class used in the binding:
public class UserTable
{
public int RowNumber { get; set; }
public string FieldName { get; set; }
public string BusinessKey { get; set; }
public string ContainsTimestamps { get; set; }
public string ContainsBASTranslatedCodes { get; set; }
public string Metadata { get; set; }
public string AdditionalComments { get; set; }
public UserTable()
{
this.RowNumber = RowNumber;
this.FieldName = FieldName;
this.BusinessKey = BusinessKey;
this.ContainsTimestamps = ContainsTimestamps;
this.ContainsBASTranslatedCodes = ContainsBASTranslatedCodes;
this.Metadata = Metadata;
this.AdditionalComments = AdditionalComments;
}
public UserTable(int number, string name, string key, string timestamps, string translated, string meta, string additional)
{
RowNumber = number;
FieldName = name;
BusinessKey = key;
ContainsTimestamps = timestamps;
ContainsBASTranslatedCodes = translated;
Metadata = meta;
AdditionalComments = additional;
}
}
The creation of the Collection:
private ObservableCollection<UserTable> _simpleCollection;
public ObservableCollection<UserTable> SimpleCollection
{
get { return _simpleCollection ?? (_simpleCollection = new ObservableCollection<UserTable>()); }
set { _simpleCollection = value; }
}
(I am unsure where to place this - Have had it in the above mentioned class, and now in the .cs file that corresponds with the XAML)
Above code results in the following UI:
As you can probably see, the user is unable to enter anything into the table - There are no empty rows for data-insertion.
Does anyone have any idea about how to fix this?
To be able to add new rows to DataGrid you can create a ViewModel class and encapsulate your ObservableCollection<T> data collection in it, like i am going to show you.
for purpose of simplifying things let's say we have User model like this:
public class User
{
public int Id { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
}
And let's create a simple view model class:
public class UsersViewModel
{
public UsersViewModel()
{
_users = new ObservableCollection<User>();
}
private ObservableCollection<User> _users;
public ObservableCollection<User> User => _users; // C# 6 feature.
}
Now we need to create an instance of UsersViewModel class and assign it as data context to any element that is parent to your DataGrid in our case let's assign it to the MainWindow Grid DataContext, we can do it in XAML or in code behind:
<Window xmlns:vm="clr-namespace:Namespace.In.Which.UsersViewModel.Lives">
<Window.Resources>
<vm:UsersViewModel x:Key="viewModel"></local:UsersViewModel>
</Window.Resources>
<Grid x:Name="mainGrid" DataContext="{StaticResource viewModel}"></Grid>
...
</Window>
Or you can do the same in code behind like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
mainGrid.DataContext = new UsersViewModel();
}
}
After that you need to bind the Users property of UserViewModel to DataGrid.ItemsSource property:
<DataGrid ...
ItemsSource="{Binding Users}"></DataGrid>
Now if you if you run your Application you will end up with an editable DataGrid.
You can set AutoGenerateColumns to true and the DataGrid will auto generate the correct columns type for your model.
I tested this steps on my machine and they work. Hopefully it helps you to solve your issue.
For the user to be able to add items in the DataGrid, your UserTable class must have a default parameterless constructor defined.
So you will either have to define one or remove the other constructor that accepts parameters:
public class UserTable
{
public int RowNumber { get; set; }
public string FieldName { get; set; }
public string BusinessKey { get; set; }
public string ContainsTimestamps { get; set; }
public string ContainsBASTranslatedCodes { get; set; }
public string Metadata { get; set; }
public string AdditionalComments { get; set; }
public UserTable() { } //<--
public UserTable(int number, string name, string key, string timestamps, string translated, string meta, string additional)
{
RowNumber = number;
FieldName = name;
BusinessKey = key;
ContainsTimestamps = timestamps;
ContainsBASTranslatedCodes = translated;
Metadata = meta;
AdditionalComments = additional;
}
}

3 master-slave Datagrids are shown horinzontally

I am a WPF newbie. I have a WPF application. And on one of WPF forms, there needs to show a 3 WPF datagrid controls whose behaviors are in cascading way.
Datagrid1 is shown on the left; datagrid2 is shown in the middle; datagrid3 is shown on the right. And those 3 datagrids, hence, are shown horizontally. Datagrid1 shows customers; datagrid2 shows orders of a specific customer; datagrid3 shows order details of a specific order.
When user clicks on a customer row of datagrid1, the application is able to load that customer's orders in datagrid2. When user clicks on an order row of datagrid2, the application is able to show order details of that order in datagrid3.
Please give me some ideas how to do that functionality. Thank you.
I made an example.
MainWindow.xaml
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:StackOverflow.ViewModel"
Title="MainWindow" Height="350" MinWidth="525">
<Window.DataContext>
<viewModel:TestViewModel />
</Window.DataContext>
<DockPanel Margin="10">
<DataGrid DockPanel.Dock="Left" MinWidth="200" Width="Auto" ItemsSource="{Binding Customers}" IsReadOnly="True" SelectedItem="{Binding SelectedCustomer}" AutoGenerateColumns="False" SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding CustomerId}" Width="Auto"></DataGridTextColumn>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"></DataGridTextColumn>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid MinWidth="200" DockPanel.Dock="Right" ItemsSource="{Binding OrderDetails}" SelectionUnit="FullRow" IsEnabled="{Binding OrderDetailsEnabled}" IsReadOnly="True" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Details ID" Binding="{Binding OrderDetailsId}" Width="Auto"></DataGridTextColumn>
<DataGridTextColumn Header="Address" Binding="{Binding OrderAddress}" Width="Auto"></DataGridTextColumn>
<DataGridTextColumn Header="Order Time" Binding="{Binding OrderTime}" Width="Auto"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid MinWidth="200" ItemsSource="{Binding Orders}" IsEnabled="{Binding OrderEnabled}" IsReadOnly="True" AutoGenerateColumns="False" SelectionUnit="FullRow" SelectedItem="{Binding SelectedOrder}">
<DataGrid.Columns>
<DataGridTextColumn Header="Order ID" Binding="{Binding OrderId}" Width="Auto"></DataGridTextColumn>
<DataGridTextColumn Header="Prod Name" Binding="{Binding ProductName}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
TestViewModel.cs
class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _orderEnable = false;
public bool OrderEnabled
{
get { return _orderEnable; }
set { _orderEnable = value; RaisePropertyChanged("OrderEnabled"); }
}
private bool _orderDetailsEnabled = false;
public bool OrderDetailsEnabled
{
get { return _orderDetailsEnabled; }
set { _orderDetailsEnabled = value; RaisePropertyChanged("OrderDetailsEnabled"); }
}
private static readonly object _lock = new object();
private ObservableCollection<Customer> _customers;
public IEnumerable<Customer> Customers
{
get { return _customers; }
}
private IEnumerable<OrderDetail> _orderDetails;
public IEnumerable<OrderDetail> OrderDetails
{
get { return _orderDetails; }
set { _orderDetails = value; RaisePropertyChanged("OrderDetails"); }
}
private IEnumerable<Order> _orders;
public IEnumerable<Order> Orders
{
get { return _orders; }
private set
{
_orders = value; RaisePropertyChanged("Orders");
OrderDetailsEnabled = false;
}
}
private Order _selectedOrder;
public Order SelectedOrder
{
get { return _selectedOrder; }
set
{
if (value != null && _selectedOrder != value)
{
_selectedOrder = value;
OrderDetails = _selectedOrder.OrderDetails;
OrderDetailsEnabled = true;
}
}
}
private Customer _selectedCustomer;
public Customer SelectedCustomer
{
get { return _selectedCustomer; }
set
{
if (value != null && _selectedCustomer != value)
{
_selectedCustomer = value;
Orders = _selectedCustomer.Orders;
OrderEnabled = true;
}
}
}
public TestViewModel()
{
_customers = new ObservableCollection<Customer>();
BindingOperations.EnableCollectionSynchronization(_customers, _lock);
_customers.Add(new Customer
{
CustomerId = 1,
FirstName = "TestFirst",
LastName = "TestLast",
Orders = new List<Order> { new Order { OrderId = 1, ProductName = "ProdOneOne",
OrderDetails = new List<OrderDetail> { new OrderDetail { OrderDetailsId = 1, OrderAddress = "EarthOneOne", OrderTime = DateTime.Now } } },
new Order { OrderId = 2, ProductName = "ProdOneTwo",
OrderDetails = new List<OrderDetail> { new OrderDetail { OrderDetailsId = 2, OrderAddress = "EarthOneTwo", OrderTime = DateTime.Now } } }}
});
_customers.Add(new Customer
{
CustomerId = 2,
FirstName = "FirstTest",
LastName = "LastTest",
Orders = new List<Order> { new Order { OrderId = 1, ProductName = "ProdTwoOne",
OrderDetails = new List<OrderDetail> { new OrderDetail { OrderDetailsId = 1, OrderAddress = "EarthTwoOne", OrderTime = DateTime.Now } } },
new Order { OrderId = 2, ProductName = "ProdTwTwo",
OrderDetails = new List<OrderDetail> { new OrderDetail { OrderDetailsId = 2, OrderAddress = "EarthTwoTwo", OrderTime = DateTime.Now } } } }
});
}
public void RaisePropertyChanged(string propertyName)
{
var pc = PropertyChanged;
if (pc != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Customer
{
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IEnumerable<Order> Orders { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public string ProductName { get; set; }
public IEnumerable<OrderDetail> OrderDetails { get; set; }
}
public class OrderDetail
{
public int OrderDetailsId { get; set; }
public DateTime OrderTime { get; set; }
public string OrderAddress { get; set; }
}
It is quite lengthy to see but very easy to understand. I hope this gets you started.

Updating ObservableCollection not Updating View on Command

I have a ListView which binds to an ObservableCollection. When my button's command updates the item in the collection, the View is not updated. I am also using Fody to implement INotifyPropertyChanged throughout.
View
<ListView ItemsSource="{Binding Path=DatabaseInfos}" VerticalAlignment="Stretch" Margin="10">
<ListView.View>
<GridView>
<GridViewColumn Header="Current Version">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding CurrentVerion}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="Test"
Command="{Binding DataContext.CmdButtonClicked, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
CommandParameter="{Binding}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
ViewModel
public interface IDatabaseViewModel
{
ObservableCollection<DatabaseInfo> DatabaseInfos { get; set; }
}
[Injectable(InstanceScope.Singleton)]
[ImplementPropertyChanged]
public class DatabaseViewModel : IDatabaseViewModel
{
private RelayCommand _buttonClicked;
private ILogger _logger;
public DatabaseViewModel()
{
_logger = ServiceLocator.Default.GetInstance<ILoggerFactory>().GetLogger(this);
DatabaseInfos = new ObservableCollection<DatabaseInfo>();
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + #"\CampusInstallerSettings.xml";
if (File.Exists(path))
{
try
{
var settings = SettingsReader.LoadSettings(path);
foreach (var db in settings.DatabaseSettings.DatabaseSettings)
{
var dbInfo = new DatabaseInfo();
if (db.ActionCode.Value == "Upgrade")
dbInfo.Action = Action.Upgrade;
else if (db.ActionCode.Value == "None")
dbInfo.Action = Action.None;
else if (db.ActionCode.Value == "Remove")
dbInfo.Action = Action.Uninstall;
else
dbInfo.Action = Action.None;
dbInfo.SqlServer = db.SQLServer.Value;
dbInfo.Database = db.Database.Value;
if (db.DBType.Value == "CampusVue")
dbInfo.DatabaseType = DatabaseType.CampusVue;
if (db.Connection.Value == "Integrated")
dbInfo.IntegratedSecurity = true;
DatabaseInfos.Add(dbInfo);
}
}
catch (Exception ex)
{
_logger.Error(ex);
throw new Exception("Could not load settings file. " + Environment.NewLine + ex.Message);
}
}
else
{
throw new Exception("Could not find settings file # " + path);
}
}
public ICommand CmdButtonClicked
{
get { return _buttonClicked ?? (_buttonClicked = new RelayCommand(ButtonClicked)); }
}
public ObservableCollection<DatabaseInfo> DatabaseInfos { get; set; }
private void ButtonClicked(object o)
{
_logger.Info(#"Test button clicked.");
var dbInfo = o as IDatabaseInfo;
if (dbInfo != null && !dbInfo.TestConnection())
{
MessageBox.Show("Couldn't connect", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
Model
public interface IDatabaseInfo
{
Action Action { get; set; }
string SqlServer { get; set; }
string Database { get; set; }
DatabaseType DatabaseType { get; set; }
bool IntegratedSecurity { get; set; }
string Username { get; set; }
string Password { get; set; }
string CurrentVersion { get; set; }
bool TestConnection();
}
[ImplementPropertyChanged]
public class DatabaseInfo : IDatabaseInfo
{
public Action Action { get; set; }
public string SqlServer { get; set; }
public string Database { get; set; }
public DatabaseType DatabaseType { get; set; }
public bool IntegratedSecurity { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string CurrentVersion { get; set; }
public bool TestConnection()
{
if (DbHelper.TestConnection(SqlServer, 1433, Database, Username, Password))
{
CurrentVersion = DbHelper.GetDbVersion(SqlServer, 1433, Database, Username, Password);
return true;
}
return false;
}
}
You're not updating the property.
public string CurrentVersion { get; set; }
is what you want to update. Because you're not calling OnPropertyChanged in the setter, the TextBox isn't going to update to a new version.
private string _currentVersion = string.Empty;
public string CurrentVersion
{
get { return _currentVersion };
set
{
_currentVersion = value;
OnPropertyChanged("CurrentVersion");
}
}
I would guess that your DatabaseInfo is your Model ;). This is where you might choose to make a small ViewModel wrapper class that returns/sets the updated values so you keep INPC out of your Model layer. Otherwise you can go with the code snippet above :)
EDIT
In that case, just spell correctly ;)
TextBox Text="{Binding CurrentVerion}"

ObservableCollection and DisplayMemberBinding

I'm trying to fill ListBox by ObservableCollection. But when I add new item nothing displayed, only empty item adding.
There are fragments of my code:
XAML
<ListView ItemsSource="{Binding Points}" SelectedItem="{Binding Point}">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header ="X" Width="100" DisplayMemberBinding = "{Binding Path=ValueX, Mode=TwoWay}" />
<GridViewColumn Header ="Y" Width="100" DisplayMemberBinding = "{Binding Path=ValueY, Mode=TwoWay}"/>
</GridView>
</ListView.View>
</ListView>
Window class
var value = new Value();
var viewModel = new ViewModel(value);
DataContext = viewModel;
InitializeComponent();
Value class
private const Point POINT = null;
private readonly ObservableCollection<Point> _points = new ObservableCollection<Point>();
public Value() {
Point = POINT;
Points = _points;
}
public Point Point { get; set; }
public ObservableCollection<Point> Points { get; private set; }
public double ValueX { get; set; }
public int ValueY { get; set; }
ViewModel class
private readonly Value _value;
public ViewModel(Value value) {
_value = value;
}
public Point Point {
get { return _value.Point; }
set {
_value.Point = value;
OnPropertyChanged("Point");
}
}
public ObservableCollection<Point> Points {
get { return _value.Points; }
}
private RelayCommand _addCommand;
public ICommand AddCommand {
get {
if (_addCommand == null) {
_addCommand = new RelayCommand(Add);
}
return _addCommand;
}
}
private void Add(object obj) {
Points.Add(new Point(ValueX, ValueY));
ValueX = 0;
ValueY = 0;
}
public double ValueX {
get {
return _value.ValueX;
}
set {
if(Math.Abs(_value.ValueX - value) < Mathematics.EPSILON) return;
_value.ValueX = value;
OnPropertyChanged("ValueX");
}
}
public int ValueY {
get { return _value.ValueY; }
set {
if(_value.ValueX == value) return;
_value.ValueY = value;
OnPropertyChanged("ValueY");
}
}
and Point class
public class Point {
public readonly double ValueX;
public readonly double ValueY;
public Point(double valueX, double valueY) {
ValueX = valueX;
ValueY = valueY;
}
public override string ToString() {
return (ValueX + " " + ValueY);
}
}
When i try to add new item, new item is added but nothing is displayed. What reason can be here?
Since you bind ItemsSource to ObservableCollection<Point> it means that each item is of a Point type which has ValueX and ValueY declared as fields which are not valid binding source. Change them to properties:
public double ValueX { get; private set; }
public double ValueY { get; private set; }
Besides you use Mode=TwoWay for something that is read only. This should be changed to OneWay. If you want to leave TwoWay binding then remove private from the setter but then also you'll need to change GridViewColumn.CellTemplate to be some TextBox instead of using DisplayMemberBinding which is for display only.

Resources