I've added a binding to an IEnumerable collection, that's populated from a async method call. The data is retrieved from a remote database and then added to the CustomerOrders list.
But after running the application, my UI binding isn't showing on the view. The view shows no data.
In order to debug the issue, I checked the following:
Checked binding and data context by binding to a static list of data.
Debugged the CustomerOrders list, after the data call, which shows as being populated after the method returns.
I also checked the thread name, and it shows as being a "main thread". (Not sure if that could be the reason as it's a different thread.)
3.1.I also implemented native INPC on the CustomerOrders property, and set a breakpoint on set which shows the list is populated. See snapshot.
Does anyone have suggestions on what the issue could be here?
The is a summary of CustomerOrderViewModel, set up as follows. A Task property, Initialization is used to call the initialization code from the constructor:
using MongoDBApp.Models;
using MongoDBApp.Services;
using MongoDBApp.Utility;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MongoDBApp.Extensions;
using System.Windows.Input;
using MongoDBApp.Common;
using MongoDBApp.Messages;
namespace MongoDBApp.ViewModels
{
[ImplementPropertyChanged]
public class CustomerOrdersViewModel : IPageViewModel, INotifyPropertyChanged
{
private IDataService<OrderModel> _orderDataService;
public CustomerOrdersViewModel(IDataService<OrderModel> orderDataService)
{
_customerOrders = new List<OrderModel>();
//{
// new OrderModel(){Email = "bvarley#gmail.com", Status = true}
//};
this._orderDataService = orderDataService;
this._dialogService = dialogservice;
Messenger.Default.Register<ProductModel>(this, OnUpdateProductMessageReceived);
this.Initialization = InitializeAsync();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#region properties
public string SelectedCustomerEmail { get; set; }
private IEnumerable<OrderModel> _customerOrders;
public IEnumerable<OrderModel> CustomerOrders
{
get { return this._customerOrders;}
set
{
_customerOrders = value;
OnPropertyChanged("CustomerOrders");
}
}
public OrderModel SelectedOrder { get; set; }
public Task Initialization { get; set; }
#endregion
#region methods
private async Task InitializeAsync()
{
var customer = await AwaitableMessages.NextMessageAsync<CustomerModel>();
SelectedCustomerEmail = customer.Email;
await LoadCustomerOrdersAsync(SelectedCustomerEmail);
}
public async Task LoadCustomerOrdersAsync(string email)
{
var ordersResult = await _orderDataService.GetAllByEmailAsync(email);
CustomerOrders = ordersResult.ToObservableCollection();
}
#endregion
}
}
This is also the associated view showing the binding setup:
<UserControl x:Class="MongoDBApp.Views.CustomerOrdersView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:boolean_converter="clr-namespace:MongoDBApp.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.Resources>
<boolean_converter:BooleanConverter x:Key="BooleanConverter" />
</UserControl.Resources>
<Viewbox>
<xctk:BusyIndicator IsBusy="{Binding ButtonEnabled}">
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding WindowLoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid x:Name="customersgrid"
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="1"
Grid.ColumnSpan="4"
AutoGenerateColumns="False"
ItemsSource="{Binding CustomerOrders}"
SelectedItem="{Binding SelectedOrder}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Email}" Header="Email" />
<DataGridTextColumn Binding="{Binding Date}" Header="Date" />
<DataGridTextColumn Binding="{Binding Status}" Header="Shipping Status" />
</DataGrid.Columns>
</DataGrid>
<Label Grid.Row="4"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Date:" />
<TextBlock Grid.Row="4"
Grid.Column="2"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding SelectedOrder.Date}"
TextWrapping="Wrap" />
<Label Grid.Row="4"
Grid.Column="3"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Products:" />
<ComboBox Grid.Row="4"
Grid.Column="4"
Grid.ColumnSpan="4"
Width="120"
HorizontalAlignment="Left"
VerticalAlignment="Top"
DisplayMemberPath="ProductId"
ItemsSource="{Binding SelectedOrder.Products}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectedItem="{Binding SelectedProduct}" />
<Label Grid.Row="5"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Email:" />
<TextBlock Grid.Row="5"
Grid.Column="2"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding SelectedOrder.Email}"
TextWrapping="Wrap" />
<RadioButton Grid.Row="5"
Grid.Column="3"
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Shipped"
IsChecked="{Binding SelectedOrder.Status,
Converter={StaticResource BooleanConverter},
ConverterParameter='true',
Mode=TwoWay}" />
<RadioButton Grid.Row="5"
Grid.Column="4"
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Processing"
IsChecked="{Binding SelectedOrder.Status,
Converter={StaticResource BooleanConverter},
ConverterParameter='false',
Mode=TwoWay}" />
</Grid>
</Grid>
</xctk:BusyIndicator>
</Viewbox>
There was nothing visibly wrong with your code; In my attempt to replicate the issue, I managed to do it without any problem. I am publishing my code here, Hope that will help.
My OrderModel class;
public class OrderModel
{
public string Email { get; set; }
public DateTime Date { get; set; }
public string Status { get; set; }
}
My ViewModel (I have used a BaseViewModel; that's optional. The way you have done is ok)
public class MainWindowViewModel:BaseViewModel
{
public MainWindowViewModel()
{
_customerOrders = new List<OrderModel>();
_customerOrders.Add(new OrderModel(){Date = DateTime.Now, Email = "mymail#gmail.com", Status = "Active"});
InitializeAsync();
}
private List<OrderModel> _customerOrders;
private OrderModel _selectedOrder;
public List<OrderModel> CustomerOrders
{
get { return this._customerOrders; }
set
{
_customerOrders = value;
OnPropertyChanged("CustomerOrders");
}
}
public OrderModel SelectedOrder
{
get { return _selectedOrder; }
set
{
_selectedOrder = value;
OnPropertyChanged("SelectedOrder");
}
}
private async void InitializeAsync()
{
CustomerOrders = await LoadCustomerOrdersAsync();
}
private async Task<List<OrderModel>> LoadCustomerOrdersAsync()
{
return await Task.Run(() => new List<OrderModel>()
{
new OrderModel() {Date = DateTime.Now, Email = "mymail1#gmail.com", Status = "Active"},
new OrderModel() {Date = DateTime.Now, Email = "mymail2#gmail.com", Status = "Active"},
new OrderModel() {Date = DateTime.Now, Email = "mymail3#gmail.com", Status = "Active"},
new OrderModel() {Date = DateTime.Now, Email = "mymail4#gmail.com", Status = "Active"},
new OrderModel() {Date = DateTime.Now, Email = "mymail5#gmail.com", Status = "Active"},
new OrderModel() {Date = DateTime.Now, Email = "mymail6#gmail.com", Status = "Active"},
});
}
}
My View
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid HorizontalAlignment="Left" Margin="31,33,0,0" VerticalAlignment="Top" Height="250" Width="455" ItemsSource="{Binding Path=CustomerOrders, Mode=TwoWay}" SelectedItem="{Binding Path=SelectedOrder, Mode=TwoWay}">
<!--<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{x:Null}" ClipboardContentBinding="{x:Null}" Header="Email" HeaderStringFormat="Email"/>
<DataGridCheckBoxColumn Binding="{x:Null}" ClipboardContentBinding="{x:Null}" Header="Date" HeaderStringFormat="Date"/>
<DataGridCheckBoxColumn Binding="{x:Null}" ClipboardContentBinding="{x:Null}" Header="Status" HeaderStringFormat="Status"/>
</DataGrid.Columns>-->
</DataGrid>
</Grid>
Code Behind;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
I came to a solution of placing the async call in the Window loaded Task, instead of the constructor.
Solution: (message handler first registers the passed over value, then the async load method is called from the window loaded Task)
public CustomerOrdersViewModel(IDataService<OrderModel> orderDataService, IDialogService dialogservice)
{
this._orderDataService = orderDataService;
this._dialogService = dialogservice;
Messenger.Default.Register<CustomerModel>(this, OnUpdateOrderMessageReceived);
LoadCommands();
}
private void OnUpdateOrderMessageReceived(CustomerModel customer)
{
SelectedCustomerEmail = customer.Email;
IsEnabled = true;
}
private async Task WindowLoadedAsync(object obj)
{
await LoadCustomerOrdersAsync(SelectedCustomerEmail);
}
Related
I am trying to create a "notes" app using wpf mvvm. I have a MainWindow containing a DataGrid with data that is bound to an ObservableCollection. In MainWindowView I have a "Find" button which calls FindWindowDialog. In the FindWindowDialog in the textbox, I must enter the text that will be searched and click "Find", after which the DataGrid MainWindowView should hide those lines whose content does not contain the searched text. I don't really know how to do this, after 2 days of searching I decided to ask a question. I googled on this topic and I have a suggestion that I should delve into the messenger pattern and converters
MainWindow.xaml(View)
<Window x:Class="NotesARK6.View.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:NotesARK6.View"
xmlns:model="clr-namespace:NotesARK6.Model"
xmlns:viewmodel="clr-namespace:NotesARK6.ViewModel"
mc:Ignorable="d"
Title="Notes" Height="450" Width="800"
x:Name="_mainWindow"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding NotesCollection}" SelectedItem="{Binding SelectedNote}" IsReadOnly="True" AutoGenerateColumns="False" x:Name="DataGrid_Notes" Margin="5" Grid.Row="2" Grid.Column="1">
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding EditNoteCommand}" CommandParameter="{Binding SelectedNote}" />
</DataGrid.InputBindings>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="1*" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Content" Width="3*" Binding="{Binding Content}"/>
</DataGrid.Columns>
</DataGrid>
<ToolBar Grid.Row="1" Grid.Column="1" Margin="5">
<Button Content="Create" Command="{Binding CreateNewNoteCommand}"/>
<Separator />
<Button Content="Delete" Command="{Binding DeleteNoteCommand}" CommandParameter="{Binding SelectedNote}"/>
<Separator />
<Button Content="Find" Command="{Binding FindNoteCommand}"/>
</ToolBar>
</Grid>
</Window>
FindWindowDialog.xaml(view)
<Window x:Class="NotesARK6.ViewModel.Dialogs.FindWindowDialog"
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:NotesARK6.ViewModel.Dialogs"
mc:Ignorable="d"
Title="Find" Height="250" Width="400"
WindowStartupLocation="CenterScreen"
Topmost="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="35"/>
<RowDefinition Height="60"/>
<RowDefinition Height="50"/>
<RowDefinition Height="90"/>
</Grid.RowDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding SearchByName}" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="5" Content="Search by name" Grid.Column="0"></CheckBox>
<CheckBox IsChecked="{Binding SearchByContent}" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="5" Content="Search by content" Grid.Column="1"></CheckBox>
</Grid>
<TextBox Text="{Binding SearchString}" Margin="5" Grid.Row="2" Grid.Column="1"/>
<Button Command="{Binding FindNotesCommand}" Margin="5" Content="Find" Grid.Row="3" Grid.Column="1" />
</Grid>
</Window>
FindWindowDialogViewModel.cs
public class FindWindowDialogViewModel : INotifyPropertyChanged
{
private string searchString;
private bool searchByName;
private bool searchByContent;
//Controll Commands
public ControllComands FindNotesCommand { get; private set; }
//Controll Commands
public FindWindowDialogViewModel()
{
FindNotesCommand = new ControllComands(FindNote);
}
public string SearchString
{
get
{
return searchString;
}
set
{
searchString = value;
OnPropertyChanged();
}
}
public bool SearchByName
{
get
{
return searchByName;
}
set
{
searchByName = value;
OnPropertyChanged("SearchByName");
}
}
public bool SearchByContent
{
get
{
return searchByContent;
}
set
{
searchByContent = value;
OnPropertyChanged("SearchByContent");
}
}
public void FindNote()
{
MessageBox.Show(SearchByName.ToString() + " " + SearchByContent.ToString());
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
How can I use the command contained in FindWindowDialogViewModel to hide rows in the DataGrid MainWindowView ?
I would like something like this: (this is pseudocode)
public void FindNote()
{
foreach(var row in MainWindow.DataGrid.Rows)
{
string searchingText = FindNoteDialog.TextBox.Text;
if (!row.Content.Contains(searchingText))
{
row.Visibillity = false;
}
}
}
For this purpose collections provide filtering via their views (see Binding to collections and CollectionView API remarks to learn more).
Filtering using the collection's view has much better performance than hiding rows or removing items from the original collection.
To do so, you must get the ICollectionView of the source collection
Note.cs
class Note
{
public string Summary { get; set; }
public DateTime Timestamp { get; set; }
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Note> Notes { get; }
public string SearchKey { get; set; }
public ICommand FilterNotesCommand => new RelayCommand(ExecuteFilterNotes);
private void ExecuteFilterCommands(object commandParameter)
{
ICollectionView notesView = CollectionViewSource.GetDefaultView(this.Notes);
notesView.Filter = item => (item as Note).Summary.Contains(this.SearchKey);
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding SearchKey}" />
<Button Command="{Binding FilterNotesCommand}" Content="Filter Table" />
<DataGrid ItemsSource="{Binding Notes}" />
</StackPanel>
</Window>
I have a WPF form that I want to use to manipulate a database table. To get the data out of the database I use Entity Framework Core. I try to follow the MVVM pattern.
So I have a Model called Employee with a number of string properties like Firstname and so on.
The form has a ListView that shows an ObservableCollection of Employee. Further I have a number of TextBoxes to display and manipulate the properties of single Employee objects. The selectedMemberPath of the ListView is bound to a property SelectedEmployee. The setter of SelectedEmployee than copies it's own properties to EditedEmployee. The TextBoxes are bound to EditedEmployee. The idea is, that I don't want to directly edit the ObservableCollection. This should only happen if a save button is clicked.
This is my view model.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using XdcCore.Models;
using XdcCore.ViewModels.Commands;
namespace XdcCore.ViewModels
{
class EmployeeEditorVM : List<Employee>, INotifyPropertyChanged
{
public EmployeeSaveCommand EmployeeSaveCommand { get; set; }
public EmployeeEditorVM()
{
EmployeeSearch();
EmployeeSaveCommand = new EmployeeSaveCommand(this);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string employeeSearchText;
public string EmployeeSearchText
{
get { return employeeSearchText; }
set
{
employeeSearchText = value;
OnPropertyChanged("EmployeeSearch");
EmployeeSearch();
}
}
private Employee selectedEmployee;
public Employee SelectedEmployee
{
get { return selectedEmployee; }
set
{
selectedEmployee = value;
OnPropertyChanged("SelectedEmployee");
EditedEmployee = SelectedEmployee;
}
}
private Employee editedEmployee;
public Employee EditedEmployee
{
get { return editedEmployee; }
set
{
editedEmployee = value;
OnPropertyChanged("EditedEmployee");
}
}
private ObservableCollection<Employee> employees;
public ObservableCollection<Employee> Employees
{
get { return employees; }
set
{
employees = value;
OnPropertyChanged("Employees");
}
}
public void EmployeeSearch()
{
using (var context = new XdcContext())
{
if (employeeSearchText != null)
{
var _employees = context.Employee
.Where(x => x.Firstname.Contains(employeeSearchText));
Employees = new ObservableCollection<Employee>(_employees);
}
else
{
var _employees = context.Employee.Select(x => x);
Employees = new ObservableCollection<Employee>(_employees);
}
}
}
public void EmployeeSave()
{
using (var context = new XdcContext())
{
var entity = context.Employee.First(x => x.IdEmployee == SelectedEmployee.IdEmployee);
{
entity.Firstname = SelectedEmployee.Firstname;
entity.Lastname = SelectedEmployee.Lastname;
entity.Initials = SelectedEmployee.Initials;
context.SaveChanges();
}
EmployeeSearch();
}
}
}
}
this is my View:
<Window
x:Class="XdcCore.Views.Editors.EmployeeEditor"
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:local="clr-namespace:XdcCore.Views.Editors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:XdcCore.ViewModels"
Title="Employee"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.Resources>
<vm:EmployeeEditorVM x:Key="vm" />
</Window.Resources>
<Grid DataContext="{StaticResource vm}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="115*" />
<ColumnDefinition Width="277*" />
</Grid.ColumnDefinitions>
<!-- left column -->
<Border BorderBrush="DarkGray" BorderThickness="1">
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="aUTO" />
</Grid.RowDefinitions>
<Label
Grid.Row="0"
Margin="3,3,3,3"
Content="Filter:" />
<TextBox
x:Name="TextBoxSearch"
Grid.Row="1"
Height="25"
Margin="3,0,3,3"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EmployeeSearchText, Mode=TwoWay}" />
<ListView
x:Name="ListViewOverview"
Grid.Row="2"
Margin="3,0,3,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding Employees, Mode=OneWay}"
SelectedItem="{Binding SelectedEmployee, Mode=OneWayToSource}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Initials}" />
<TextBlock Text=" : " />
<TextBlock Text="{Binding Firstname}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding Lastname}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
x:Name="ButtonDelete"
Grid.Column="0"
Height="25"
Margin="3,3,3,3"
Content="Delete"
IsEnabled="False" />
<Button
x:Name="ButtonNew"
Grid.Column="1"
Height="25"
Margin="3,3,3,3"
Content="Add New ..."
IsEnabled="True" />
</Grid>
</Grid>
</Border>
<!-- right Column -->
<Grid Grid.Column="1" Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- right column, first row -->
<Border
Grid.Row="0"
BorderBrush="DarkGray"
BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Right"
Content="First Name" />
<Label
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Right"
Content="Last Name" />
<Label
Grid.Row="2"
Grid.Column="0"
HorizontalAlignment="Right"
Content="Initials" />
<Label
Grid.Row="3"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
Content="ID" />
<Label
Grid.Row="4"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
Content="RCU" />
<Label
Grid.Row="5"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
Content="RCT" />
<TextBox
x:Name="TextBoxFirstName"
Grid.Row="0"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EditedEmployee.Firstname, Mode=TwoWay}" />
<TextBox
x:Name="TextBoxLastName"
Grid.Row="1"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EditedEmployee.Lastname, Mode=TwoWay}" />
<TextBox
x:Name="TextBoxInitials"
Grid.Row="2"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EditedEmployee.Initials, Mode=TwoWay}" />
<TextBox
x:Name="TextBoxId"
Grid.Row="3"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
IsReadOnly="True" />
<TextBox
x:Name="TextBoxRCU"
Grid.Row="4"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
IsReadOnly="True" />
<TextBox
x:Name="TextBoxRCT"
Grid.Row="5"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
IsReadOnly="True" />
</Grid>
</Border>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
x:Name="ButnSave"
Grid.Column="1"
Width="76"
Height="25"
Margin="60,3,60,0"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Command="{Binding EmployeeSaveCommand}"
Content="Save"
IsEnabled="True" />
</Grid>
<!-- Add new -->
</Grid>
</Grid>
</Grid>
</Window>
In other words: I want to click on an item in the list view, it's details are shown in the text boxes. I can change the text in the text boxes but the changes are not applied to the observable collection (or even database) until I click "save". The problem is, that exactly that happens: when I edit text in the text boxes the changes are also shown in the list view. And I can#t see why.
So, I got the concept of "copy vs point" of a variable wrong.
I had two solutions for my problem.
To my Employee model I added the method Copy:
public partial class Employee
{
public int IdEmployee { get; set; }
public string Initials { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Rcu { get; set; }
public DateTime Rct { get; set; }
public Employee Copy()
{
return (Employee)this.MemberwiseClone();
}
}
Then I Changed my ViewModel so that EditedEmployee is a copy of SelectedEmployee
private Employee selectedEmployee;
public Employee SelectedEmployee
{
get { return selectedEmployee; }
set
{
selectedEmployee = value;
OnPropertyChanged("SelectedEmployee");
if (SelectedEmployee != null)
{
EditedEmployee = SelectedEmployee.Copy();
}
}
}
private Employee editedEmployee;
public Employee EditedEmployee
{
get { return editedEmployee; }
set
{
editedEmployee = value;
OnPropertyChanged("EditedEmployee");
}
}
The problem with this approach is, that if I mess around with the database and then need to run Scaffold-DbContext (I prefer the code-first approach of EFC) again, I would have to modify all models again. A solution to that might be some a second, modified Model that inherits from the original Model and then adds the Copy method.
My second approach is to bind the ListView via SelectedIndex instead of SelectedItem.
<ListView
...
ItemsSource="{Binding Employees, Mode=OneWay}"
SelectedIndex="{Binding SelectedItemIndex, Mode=TwoWay}">
In the ViewModel this is then represented by an int property. If this int is set (and >= 0) Employee gets copied EmployeeWorkingCopy. Furthermore if EmployeeWorkingCopy is set a single Employee dubbed EditedEmployee is set as the Employee of the working copy with the selected index.
private int selectedItemIndex;
public int SelectedItemIndex
{
get { return selectedItemIndex; }
set
{
selectedItemIndex = value;
if (SelectedItemIndex >= 0)
EmployeesWorkingCopy = new ObservableCollection<Employee>(Employees);
}
}
private Employee editedEmployee;
public Employee EditedEmployee
{
get { return editedEmployee; }
set
{
editedEmployee = value;
OnPropertyChanged("EditedEmployee");
}
}
private ObservableCollection<Employee> employeesWorkingCopy;
public ObservableCollection<Employee> EmployeesWorkingCopy
{
get { return employeesWorkingCopy; }
set
{
employeesWorkingCopy = value;
EditedEmployee = EmployeesWorkingCopy[SelectedItemIndex];
}
}
Thanks a lot for pointing out my misconception.
I cant seem to figure out how to get data from wfp into a list and bind to datagrid.
The xaml form is displaying alright
I have created properties, I created a class to inherit the base class which is code360. I have tried a lot of resources online but not working for me. I tried to hard code the input to see if it will populate the grid but no way. Like this
{firstName = "Tim", lastName = "Joy",
email = "tim#joy.com",
phoneNumber = "0988390243",
amount = 200000 }
This is mainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Windows;
namespace Code360
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public class code360
{
public string firstName { get; set; }
public string lastName { get; set; }
public string email { get; set; }
public string phoneNumber { get; set; }
public decimal amount { get; set; }
}
public class Code360Manager : List<code360>
{
public Code360Manager()
{
Add(new code360() {firstName = "Tim", lastName = "Joy", email = "tim#joy.com", phoneNumber = "0988390243", amount = 200000 });
}
}
private void SubmitButton_Click(object sender, RoutedEventArgs e)
{
//studentGrid.ItemsSource = Code360Manager();
MessageBox.Show("This is to test the firstname", firstName.Text);
}
}
}
This is mainWindow.xaml
<Window x:Class="Code360.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:Code360"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="1" FontSize="36"
Text="Code360 Student Record" Grid.ColumnSpan="3"
Margin="0,0,0,10" />
<TextBlock Grid.Column="1" Grid.Row="2"
Text="First Name" Margin="0,0,0,10"/>
<TextBox x:Name="firstName" Grid.Column="2" Grid.Row="2"
Width="150" Margin="0,0,0,10"/>
<TextBlock Grid.Column="3" Grid.Row="2" Text="Last Name" Margin="0,0,0,10"/>
<TextBox x:Name="lastName" Grid.Column="4" Grid.Row="2" Width="150" Margin="0,0,0,10"/>
<TextBlock Grid.Column="1" Grid.Row="3"
Text="Email" Margin="0,0,0,10"/>
<TextBox x:Name="email" Grid.Column="2" Grid.Row="3"
Width="150" Margin="0,0,0,10"/>
<TextBlock Grid.Column="3" Grid.Row="3"
Text="Phone Number" Margin="0,0,0,10"/>
<TextBox x:Name="phonenumber" Grid.Column="4" Grid.Row="3"
Width="150" Margin="0,0,0,10"/>
<TextBlock Grid.Column="1" Grid.Row="4"
Text="Amount" Margin="0,0,0,10"/>
<TextBox x:Name="amount" Grid.Column="2" Grid.Row="4"
Width="150" Margin="0,0,0,10"/>
<Button x:Name="submitButton" Content="Submit"
Grid.Column="4" Grid.Row="4"
Margin="0,0,0,10" Click="SubmitButton_Click" />
<DataGrid x:Name="studentGrid" Grid.Column="1" Grid.Row="5" MinWidth="20" Grid.ColumnSpan="5" Margin="22,0,45,154" Grid.RowSpan="2" ItemsSource="{StaticResource Code360Manager}">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding firstName}" />
<DataGridTextColumn Header="Last Name" Binding="{Binding lastName}" />
<DataGridTextColumn Header="Email"
Binding="{Binding email}" />
<DataGridTextColumn Header="Phone Number" Binding="{Binding phonenumber}" />
<DataGridTextColumn Header="Amount" Binding="{Binding amount}" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Details}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
</Grid>
</Window>
1.To use Binding u need to set the DataContext property
so
InitializeComponent();
this.DataContext = this;
2.u need a collection to bind the DataGrid in xaml.cs
List<code360> MyList { get; set; }// make sure u have data in this list
3.ItemsSource prop will take collection so u need to do bind it with collection
ItemsSource="{Binding MyList } // note the list name is same as the in 2 ponit
also this is not following mvvm
I pasted that code in my Visual Studio and among other things, I came across a problem with this line ItemsSource="{StaticResource Code360Manager}"
From the XAML you shared I cannot see where you defiend the resource so consider something like the following.
<Grid.Resources>
<local:Code360Manager x:Key="Code360Manager" />
</Grid.Resources>
after the definition of grid and you'll see the datagrid render
I believe that you need to set DataContext.
After InitializeComponent();:
DataContext = this;
Edit: (Sorry. I sent the comments badly).
You need to set the xaml to the context. Generally using a MainWindowViewModel.
I change your code to set the ItemsSource to a List<> instead a class:
public partial class MainWindow : Window
{
public List<code360> Code360Manager { get; set; } = new List<code360>();
public MainWindow()
{
InitializeComponent();
Code360Manager.Add(new code360() {firstName = "Tim", lastName = "Joy", email = "tim#joy.com", phoneNumber = "0988390243", amount = 200000 });
DataContext = this;
}
private void SubmitButton_Click(object sender, RoutedEventArgs e)
{
//studentGrid.ItemsSource = Code360Manager();
MessageBox.Show("This is to test the firstname", firstName.Text);
}
}
public class code360
{
public string firstName { get; set; }
public string lastName { get; set; }
public string email { get; set; }
public string phoneNumber { get; set; }
public decimal amount { get; set; }
}
A Grid will not take data input as ItemSource, you need to use DataContext. Something like:
studentGrid.DataContext = Code360Manager();
If it still does not work, try using ListCollectionView as following:
ListCollectionView alist = new ListCollectionView(Code360Manager());
studentGrid.DataContext = alist;
My application has a grid with two columns. In the left column, I have a ListView, with some items with data binding. On the Right I want to implement a View that will display content based on which item
is selected on the ListView. How do I do that?
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Background="LightBlue">
<Grid Name="MainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<ListView Grid.Column="0" Name="listView" ItemsSource="{Binding Path=ClientCollection}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ClientName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
I believe the best way to achieve that is to add a selectedItem property to you ViewModel, witch is binded to the selected item of your ListView, and make that property the dataContext of the Right View;
here is a an example :
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView Grid.Column="0" x:Name="LvListView" ItemsSource="{Binding ClientCollection}" SelectedItem="{Binding SelectedClient}" >
<ListView.View>
<GridView>
<GridViewColumn Width="150" Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Width="150" Header="Age" DisplayMemberBinding="{Binding Age}"/>
<GridViewColumn Width="150" Header="Location" DisplayMemberBinding="{Binding Location}"/>
</GridView>
</ListView.View>
</ListView>
<Grid Grid.Column="1" VerticalAlignment="Center" DataContext="{Binding SelectedClient,Mode=TwoWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Name: " Grid.Column="0" Grid.Row="0"/>
<TextBlock Text="Age: " Grid.Column="0" Grid.Row="1"/>
<TextBlock Text="Location: " Grid.Column="0" Grid.Row="2"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Name}"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Age}"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Location}"/>
</Grid>
</Grid>
and in the coresponding MainWindow.cs :
public partial class MainWindow : Window, INotifyPropertyChanged
{
private Client _selectedClient = new Client();
public Client SelectedClient
{
get { return _selectedClient; }
set
{
if (_selectedClient == value) return;
_selectedClient = value;
OnPropertyChanged();
}
}
private ObservableCollection<Client> _clientCollection = new ObservableCollection<Client>();
public ObservableCollection<Client> ClientCollection
{
get { return _clientCollection; }
set
{
if (_clientCollection == value) return;
_clientCollection = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
ClientCollection = new ObservableCollection<Client>()
{
new Client()
{
Name = "James",Age = 34, Location = "Paris"
},
new Client()
{
Name = "Joe",Age = 34, Location = "Us"
},
new Client()
{
Name = "Ann",Age = 34, Location = "Canada"
},
};
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Client
{
public string Name { get; set; }
public string Location { get; set; }
public int Age { get; set; }
}
and don't forget to set the datacontext for the mainWindow View, in that case
DataContext="{Binding RelativeSource={RelativeSource Self}}"
I'm just having a play with WPF, and I'm having a problem with databinding...
Here's my code so far...
The Window XAML:
<Window x:Class="FRC.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Registry Cleaner - By Martin Milan." MinHeight ="350" Height ="350" MinWidth="525" MaxHeight="700" Width="350" Background="#FFC199AA" >
<DockPanel Background="#FFD9E1E8" Margin="10">
<Grid DockPanel.Dock="Top" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right" >Filepath:</Label>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" Name="txtFilePath" VerticalAlignment="Stretch" />
</Grid>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" >
<Button Name="butScan" Content="Scan" MinWidth="75" Margin="0,0,10,5" />
<Button Name="butDelete" Content="Remove RegKeys" Margin="0,0,5,5" Click="butDelete_Click" />
</StackPanel>
<ScrollViewer Margin="0,0,0,5">
<DataGrid AutoGenerateColumns="False" Name="dgActions" CanUserAddRows="False" CanUserDeleteRows="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding RegKeyPath, Mode=OneWay}" Header="Registry Key" Width="*"/>
<DataGridCheckBoxColumn Binding="{Binding DeletePath, Mode=TwoWay}" Header="Can I delete key?"
MinWidth="110" Width="110" />
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
</DockPanel>
</Window>
The code behind for the window:
namespace FRC
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
protected List<RegistryAction> mRegistryActions = new List<RegistryAction>();
public MainWindow()
{
InitializeComponent();
RegistryAction oRegAction = new RegistryAction();
oRegAction.DeletePath = true;
oRegAction.RegKeyPath = "A test value";
mRegistryActions.Add (oRegAction);
dgActions.DataContext = mRegistryActions;
dgActions.ItemsSource = mRegistryActions;
}
private void butDelete_Click(object sender, RoutedEventArgs e)
{
RegistryAction oRegAction = new RegistryAction();
oRegAction.DeletePath = true;
Random rGen = new Random();
oRegAction.RegKeyPath = "A test " + rGen.Next(100).ToString();
mRegistryActions.Add(oRegAction);
}
}
}
The RegistryAction class:
namespace FRC
{
public class RegistryAction
{
public string RegKeyPath { get; set; }
public bool DeletePath { get; set; }
public RegistryAction()
{
this.DeletePath = false;
this.RegKeyPath = "";
}
}
}
Basically. it sets up a list of RegistryAction objects, and binds it with a DataGrid. I am finding however that whenever I run the code in butDelete_Click, although the list is updated, the content isn't getting updated on the Grid.
In short, can anyone spot what I have missed please?
Martin.
mRegistryActions should be ObservableCollection:
protected ObservableCollection<RegistryAction> mRegistryActions = new ObservableCollection<RegistryAction>();
You need to have RegistryAction implement INotifyPropertyChanged. MSDN also has a how-to on this subject.