I am trying to build a datagrid where columns are generated dynamically (this works fine) but I am unable to create bindings for the columns which update automatically (something like INotifyPropertyChanged).
Creating columns dynamically and want to use dictionary elements for binding which can be modified/added dynamically. No errors seen in debug output of visual studio.
I think I am really missing something minor here.
clicking button does not populate the second column
ViewModel:
class DataGridAttachedPropertyViewModel {
public ObservableCollection<DataGridColumn> ColumnCollection { get; set; }
public ObservableCollection<AttachedPropertyEmployee> SomEmployees { get; set; }
public ICommand myCommand { get; set; }
public DataGridAttachedPropertyViewModel() {
this.ColumnCollection = new ObservableCollection<DataGridColumn>();
DataGridTextColumn tc = new DataGridTextColumn();
tc.Header = "Sample Column";
// tc.Binding = new Binding("name");
Binding forCurrent = new Binding("SimpleDict[f]");
forCurrent.Mode = BindingMode.TwoWay;
tc.Binding = forCurrent;
DataGridTextColumn tt = new DataGridTextColumn();
tt.Header = "Column x";
// tc.Binding = new Binding("name");
Binding forTheCurrent = new Binding("SimpleDict[x]");
forTheCurrent.Mode = BindingMode.TwoWay;
tt.Binding = forTheCurrent;
myCommand = new DelegateCommand(ButtonBase_OnClick);
this.ColumnCollection.Add(tc);
this.SomEmployees = new ObservableCollection<AttachedPropertyEmployee>();
this.SomEmployees.Add(new AttachedPropertyEmployee("Rajat","Norwalk"));
this.SomEmployees.Add(new AttachedPropertyEmployee("Matthew", "Norwalk"));
}
public void ButtonBase_OnClick() {
foreach (var VARIABLE in SomEmployees) {
VARIABLE.SimpleDict["x"] = "x";
}
}
}
AttachedPropertyEmployee.cs
public class AttachedPropertyEmployee : INotifyPropertyChanged {
private Dictionary<string, string> dict;
public Dictionary<string, string> SimpleDict {
get { return this.dict; }
set
{
if (this.dict != value) {
this.dict = value;
this.NotifyPropertyChanged("SimpleDict");
}
}
}
public AttachedPropertyEmployee(string Name, string Address) {
this.SimpleDict = new Dictionary<string, string>();
SimpleDict["f"] ="b";
this.name = Name;
this.address = Address;
}
public string name;
public string address { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName) {
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
XAML:
<Window x:Class="LearnInteractivity.LearnDataGridAttachedProperty"
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:LearnInteractivity"
mc:Ignorable="d"
Title="LearnDataGridAttachedProperty" Height="300" Width="300">
<!--
Put a datargrid and an attached property and update columns dynamincally.
-->
<StackPanel>
<DataGrid
local:DataGridColumnsBehavior.BindableColumns="{Binding ColumnCollection}"
x:Name="dgg"
AutoGenerateColumns="False"
ItemsSource="{Binding SomEmployees}"></DataGrid>
<Button Content="Populate" Command="{Binding myCommand}"></Button>
</StackPanel>
I see two problems here.
The first is that Dictionary<TKey,TValue> doesn't implement INotifyCollectionChanged, so when you change values in it, no event is raised and the UI never knows about it. You could look for an ObservableDictionary<K,V> and use that (IIRC there are a few implementations around), or you can do it the quick and dirty way:
public void ButtonBase_OnClick() {
foreach (var VARIABLE in SomEmployees) {
VARIABLE.SimpleDict["x"] = "x";
VARIABLE.NotifyPropertyChanged("SimpleDict");
}
}
That will notify the grid that SimpleDict has changed.
The second problem is that in the DataGridAttachedPropertyViewModel constructor, you forgot to add tt to ColumnCollection.
this.ColumnCollection.Add(tc);
this.ColumnCollection.Add(tt);
More thoughts:
I would be more comfortable adding something like this to AttachedPropertyEmployee:
public void SetColumValue(string key, string value) {
SimpleDict[key] = value;
NotifyPropertyChanged("SimpleDict");
}
And use that in your loop instead:
public void ButtonBase_OnClick() {
foreach (var VARIABLE in SomEmployees) {
VARIABLE.SetColumnValue("x", "x");
}
}
Incidentally, I'd change SimpleDict to Dictionary<String, Object> so you can support more types than just string, and leave formatting to the UI. And I might consider exposing a ReadOnlyDictionary<K,V> in the SimpleDict property, with the writable dictionary a private field -- so callers would have no choice but to use SetColumnValue(k,v) to set the column values.
Related
My code looks like this right now with two lines of code for each message. The code works but if I have for example 30 messages that I can each give values to then I will need to have 60 lines of code just to declare everything:
string _msg1;
string _msg2;
public string Msg1 { get => _msg1; set => SetProperty(ref _msg1, value); }
public string Msg2 { get => _msg2; set => SetProperty(ref _msg2, value); }
and in C# I assign to these:
vm.Msg1 = "A";
vm.Msg2 = "B";
and in the XAML I bind my Text to Msg1 and another Text to Msg2
Can someone tell me how / if I can do this with array so that I would assign like this and hopefully so the assignment of the array can just be done in two lines instead of 2 lines for every single message:
vm.Msg[0] = "A";
vm.Msg[1] = "B";
For reference:
public class ObservableObject : INotifyPropertyChanged
{
protected virtual bool SetProperty<T>(
ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
You can create a simple wrapper class with indexing that supports property change notification.
For example:
public class Messages : ObservableObject
{
readonly IDictionary<int, string> _messages = new Dictionary<int, string>();
[IndexerName("Item")] //not exactly needed as this is the default
public string this[int index]
{
get
{
if (_messages.ContainsKey(index))
return _messages[index];
//Uncomment this if you want exceptions for bad indexes
//#if DEBUG
// throw new IndexOutOfRangeException();
//#else
return null; //RELEASE: don't throw exception
//#endif
}
set
{
_messages[index] = value;
OnPropertyChanged("Item[" + index + "]");
}
}
}
And, create a property in view model as:
private Messages _msg;
public Messages Msg
{
get { return _msg ?? (_msg = new Messages()); }
set { SetProperty(ref _msg, value); }
}
Now you can set or update values as:
vm.Msg[0] = "A";
vm.Msg[1] = "B";
Bindings in XAML will be same as:
<Label Text="{Binding Msg[0]}" />
<Label Text="{Binding Msg[1]}" />
Sample usage code
XAML
<StackLayout Margin="20">
<Label Text="{Binding Msg[0]}" />
<Label Text="{Binding Msg[1]}" />
<Label Text="{Binding Msg[2]}" />
<Label Text="{Binding Msg[3]}" />
<Label Text="{Binding Msg[4]}" />
<Button Text="Trigger update" Command="{Binding UpdateMessage}" />
</StackLayout>
Code-behind, view-model
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
var viewModel = new MainViewModel();
viewModel.Msg[0] = "Original message 1";
viewModel.Msg[1] = "Original message 2";
viewModel.Msg[2] = "Original message 3";
viewModel.Msg[3] = "Original message 4";
viewModel.Msg[4] = "Original message 5";
BindingContext = viewModel;
}
}
public class MainViewModel : ObservableObject
{
private Messages _msg;
public Messages Msg
{
get { return _msg ?? (_msg = new Messages()); }
set { SetProperty(ref _msg, value); }
}
public ICommand UpdateMessage => new Command(() =>
{
Msg[2] = "New message 3";
Msg[0] = "New message 1";
});
}
Arrays will not raise property changed event. You'll need to use an ObservableCollection that can raise an event when the collection has changed. However, this doesn't raise an event when the object inside the collection has changed it's value. You'll need to wrap your object, in this case a string, into a type that can raise property changed events.
Something like the following would work:
public class BindableValue<T> : INotifyPropertyChanged
{
private T _value;
public T Value
{ get => _value; set => SetProperty(ref _value, value); }
// INotifyPropertyChanged and SetProperty implementation goes here
}
private ObservableCollection<BindableValue<string>> _msg;
public ObservableCollection<BindableValue<string>> Msg
{ get => _msg; set => SetProperty(ref _msg1, value); }
you would be binding to Msg[0].Value, Msg[1].Value etc.,
Not entirely sure that I got the question, but as I understood the simplest way is this:
The Viewmodel:
Just bind to an ObservableCollection of strings, because it already implements INotifyCollectionChanged and INotifyPropertyChanged.
RelayCommand is just an implementation of ICommand and I'm assuming you have heard of them since you are doing WPF MVVM.
using System.Collections.ObjectModel;
namespace WpfApp1
{
public class MainWindowViewmodel
{
public ObservableCollection<string> Messages { get; set; }
public MainWindowViewmodel()
{
Messages = new ObservableCollection<string>();
Messages.Add("My message!");
ChangeMessageCommand = new RelayCommand(ChangeMessageExcecute);
}
public RelayCommand ChangeMessageCommand { get; set; }
private void ChangeMessageExcecute() => Messages[0] = "NEW message!";
}
}
The View:
In the view you can just bind your Textblocks to the Elements of the ObservableCollection. When you press the button, the Command gets called and changes the message in the window.
<Window x:Class="WpfApp1.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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Messages[0]}" HorizontalAlignment="Center"/>
<Button Content="Change Message" Command="{Binding ChangeMessageCommand}" Width="200"/>
</StackPanel>
</Grid>
</Window>
Kind regards,
misdirection
I Assume that your given example is running and working as expected (Atleast with 2 items)
View Code.
Assuming you want to show all the 30 messages as a list.
<ListView ItemsSource="{Binding MessagesArray}"/>
Also you should set the DataContext properly, Comment below if you need any help
View Model Code.
We are using an ObservableCollection instead of array. Since pure arrays doesn't support proper binding features.
private ObservableCollection<string> _messagesArray;
public ObservableCollection<string> MessagesArray
{
get { return _messagesArray; }
set { SetProperty(ref _messagesArray, value); }
}
Assigning Values
MessagesArray = new ObservableCollection<string>();
vm.MessagesArray.Add("A");
vm.MessagesArray.Add("B");
In the assignment code MessagesArray = new ObservableCollection<string>(); assigns a new object of ObservableCollection of String
If you are new to ObservableCollection think of this as an wrapper to string[], but not actually true
SetProperty method will tell the XAML View that a new collection is arrived, so the UI will rerender the list.
When you call vm.MessagesArray.Add("B"); internal logics inside the method Add will tell the XAML View a new item is added to the ObservableCollection so the view can rerender the ListView with the new item.
Update 27 October 2018
You can create your own array using any of the below ways. (Not all)
string[] dataArray = new string[30];
1. this will create an array with 30 null values
string[] dataArray = { "A", "B", "C" }; //Go up to 30 items
2. this will create an array with predefined set of values, you can go up to 30
string[] dataArray = Enumerable.Repeat<string>(String.Empty, 30).ToArray();
3. this will create an array with string which holds empty values, Instead of String.Empty you can put any string value.
Choose any of the above method
I recommend the last method, then you can assign that into a Observable Collection like below.
MessagesArray = new ObservableCollection<string>(dataArray);
Now the trick is
vm.MessagesArray[0] = "A"
vm.MessagesArray[25] = "Z"
View might look like below
<TextBlock Text="{Binding MessagesArray[0]}"/>
<TextBlock Text="{Binding MessagesArray[1]}"/>
What about using reflection?
You can ask for all the public properties of type string with name "Msg*".
For example:
static class Program
{
static void Main(string[] args)
{
var vm = new MessagesViewModel();
PropertyInfo[] myProperties = vm.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.PropertyType == typeof(string) && p.Name.Contains("Msg"))
.ToArray();
foreach (var propertyInfo in myProperties)
{
//You can also access directly using the indexer --> myProperties[0]..
propertyInfo.SetValue(vm, $"This is {propertyInfo.Name} property");
}
Console.WriteLine(vm.Msg1);
Console.WriteLine(vm.Msg2);
}
}
public class MessagesViewModel
{
string _msg1;
string _msg2;
public string Msg1 { get => _msg1; set => _msg1 = value; }
public string Msg2 { get => _msg2; set => _msg2 = value; }
}
If this type of solution fits, you can wrap it with an indexer, sort the array to match the index and the Msg[num].
I see several other posts about this but I cannot seem to understand exactly how to get this working properly for my usage.
Here is what I have in a nutshell.
I have two Comboboxes--Role and Position.
I have both of these bound to an ObservableCollection which has Enum Values Converted to strings loaded into it on instantiation.
<ComboBox x:Name="empRoleCB" ItemsSource="{Binding Role}" SelectedItem="{Binding RoleStr}"/>
<ComboBox x:Name="empPositionCB" ItemsSource="{Binding Pos}" SelectedItem="{Binding PosStr}"/>
In my ViewModel:
public abstract class EmployeeMenuVMBase : 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;
}
}
class EmployeeMenuVM : EmployeeMenuVMBase
{
private ObservableCollection<string> _pos = new ObservableCollection<string>(Enum.GetNames(typeof(Global.Positions)));
private ObservableCollection<string> _role = new ObservableCollection<string>(Enum.GetNames(typeof(Global.Roles)));
public ObservableCollection<string> Pos { get => _pos; }
public ObservableCollection<string> Role { get => _role; }
public string RoleStr
{
get => _roleStr;
set => SetProperty(ref _roleStr, value);
}
public string PosStr
{
get => _posStr;
set => SetProperty(ref _posStr, value);
}
}
What I want to happen is when a Role is selected, based on that selection, only certain Positions should be shown. For instance if I select "Customer Service" as a Role, then Position should only contain "Manager", "CSS" and "None". If Role is "Admin" then Position should only contain "None", and so on and so forth.
The struggle I have is how to filter this properly. I see something with using CollectionViewSource but I am unsure how to get this to work with my example.
I have 5 roles and each role will have a different list of positions that need to be shown.
What is the best way to make this work with MINIMAL extra code or XAML?
One of the things I really dislike about WPF is seemingly simple things need huge amounts of code to make them work properly many times.
First, if you think that WPF is complicated. So, you are using it wrongly.
I suggest you to use the Filter of CollectionViewSource as flow:
<ComboBox x:Name="empPositionCB" ItemsSource="{Binding MyPositionFilter}" SelectionChanged="RoleComboBox_SelectionChanged" ....../>
public ICollectionView MyPositionFilter { get; set; }
//ctor
public MyUserControlOrWindow()
{
//Before InitComponent()
this.MyPositionFilter = new CollectionViewSource { Source = MyPosObservableCollection }.View;
InitComponent();
}
public void RoleComboBox_SelectionChanged(object sender,EventArgs e)
{
//Get the selected Role (the ? is to prevent NullException (VS 2015 >))
Role r = empRoleCB.SelectedItem as Role;
//Apply the filter
this.MyPositionFilter.Filter = item =>
{
//Make you sure to convert correcteley your Enumeration, I used it here like a class
Position p = item as Position;
//Put your condition here. For example:
return r.ToLowers().Contains(p.ToLower());
//Or
return (r != null && r.Length >= p.Length);
};
}
The filter does not change your Collection, All hidden item stay in your ObservableCollection.
This can all be done in your ViewModel by changing the value of the Positions (Pos) observable collection when the role changes.
class EmployeeMenuVM : EmployeeMenuVMBase
{
public EmployeeMenuVM()
{
var emptyPositions = new List<Global.Positions>()
{ Global.Positions.None };
_rolePositions.Add(Global.Roles.None, emptyPositions);
var customerServicePositions = new List<Global.Positions>()
{ Global.Positions.None, Global.Positions.CSS, Global.Positions.Manager };
_rolePositions.Add(Global.Roles.CustomerService, customerServicePositions);
}
private Dictionary<Global.Roles, List<Global.Positions>> _rolePositions = new Dictionary<Global.Roles, List<Global.Positions>>();
private string _roleStr;
private string _posStr;
private ObservableCollection<string> _pos = new ObservableCollection<string>(Enum.GetNames(typeof(Global.Positions)));
private ObservableCollection<string> _role = new ObservableCollection<string>(Enum.GetNames(typeof(Global.Roles)));
public ObservableCollection<string> Pos
{
get => _pos;
set
{
SetProperty(ref _pos, value);
}
}
public ObservableCollection<string> Role
{
get => _role;
}
public string RoleStr
{
get => _roleStr;
set
{
if (SetProperty(ref _roleStr, value))
{
Global.Roles role = (Global.Roles)Enum.Parse(typeof(Global.Roles), value);
var positions = _rolePositions[role].Select(p => p.ToString());
Pos = new ObservableCollection<string>(positions);
}
}
}
public string PosStr
{
get => _posStr;
set => SetProperty(ref _posStr, value);
}
}
Here is a working tester code just to see the main idea of how to do the filtering:
MainWindow.xaml
<Window x:Class="WpfApplication3.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:WpfApplication3"
x:Name="ThisView"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="600">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=Roles, ElementName=ThisView}"
SelectedItem="{Binding Path=SelectedRole, ElementName=ThisView}"
Width="300" Height="60"/>
<ComboBox ItemsSource="{Binding Path=PositionCollectionView, ElementName=ThisView}" Width="300" Height="60"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public ICollectionView PositionCollectionView { get; set; }
public ObservableCollection<string> Roles { get; set; } = new ObservableCollection<string>();
public ObservableCollection<string> Positions { get; set; } = new ObservableCollection<string>();
private string _selectedRole = String.Empty;
public string SelectedRole
{
get { return _selectedRole; }
set
{
_selectedRole = value;
OnPropertyChanged();
//This Refresh activates the Filter again, so that every time you select a role, this property will call it.
PositionCollectionView.Refresh();
}
}
public MainWindow()
{
PositionCollectionView = CollectionViewSource.GetDefaultView(Positions);
PositionCollectionView.Filter = PositionsFilter;
//use you enums here
Roles.Add("Role1");
Roles.Add("Role2");
Roles.Add("Role3");
Roles.Add("Role4");
Positions.Add("Position1");
Positions.Add("Position2");
Positions.Add("Position3");
Positions.Add("Position4");
InitializeComponent();
}
private bool PositionsFilter(object position)
{
bool result = true;
//place your code according to the Role selected to decide wheather "position" should be in the position list or not
return result;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Hope it helps..
I am really struggling with data binding and the MVVM Methodology, though I like the concept I am just struggling. I have created a WPF for that has multiple comboboxes and a button. The first combobox will list database instance names. the remaining comboboxes will be populated after the button is clicked. Since I am having issues with the first, database instances, combobox I will only show my code for that. When the application starts up the combobox is loaded and the first item is selected, as expected. The issue is when I select a new name my method that I expect to get called does not. Can someone help me to understand why my method public DBInstance SelectedDBInstance is not getting executed when I have this in my XAML, SelectedValue="{Binding SelectedDBInstance, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}?
Here is my XAML for the database instances combobox. One question I have here is the "value" fpr SelectedValuePath, if I change it to say "DBInstanceName" it does not work.
<ComboBox x:Name="cbxRLFDBInstances" ItemsSource="{Binding DBInstances}"
SelectedValue="{Binding SelectedDBInstance, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="value" DisplayMemberPath="DBInstanceName"/>
Here is my ViewModel Code:
namespace DatabaseTest.ViewModel
{
class RLFDatabaseTableViewModel : INotifyPropertyChanged
{
Utilities dbtUtilities = new Utilities();
public RelayCommand LoadDBInfoCommand
{
get;
set;
}
public RLFDatabaseTableViewModel()
{
LoadDBInstances();
LoadDBInfoCommand = new RelayCommand(LoadDBInfo);
}
public ObservableCollection<DBInstance> DBInstances
{
get;
set;
}
public void LoadDBInstances()
{
ObservableCollection<DBInstance> dbInstances = new ObservableCollection<DBInstance>();
DataTable dt = SmoApplication.EnumAvailableSqlServers(false);
dbInstances.Add(new DBInstance { DBInstanceName = "fal-conversion\\mun2012ci" });
dbInstances.Add(new DBInstance { DBInstanceName = "fal-conversion\\mun2014ci" });
if (dt.Rows.Count > 0)
{
foreach (DataRow dr in dt.Rows)
{
dbInstances.Add(new DBInstance { DBInstanceName = dr["Name"].ToString() });
}
}
DBInstances = dbInstances;
}
private DBInstance _selectedDBInstance;
public DBInstance SelectedDBInstance
{
get
{
return _selectedDBInstance;
}
set
{
_selectedDBInstance = value;
RaisePropertyChanged("SelectedDBInstance");
//ClearComboBoxes();
}
}
}
}
Here is my Model code. When I step through the code this method, public string DBInstanceName, gets executed multiple time. I do not know why and it is seems wasteful to me.
namespace DatabaseTest.Model
{
public class RLFDatabaseTableModel { }
public class DBInstance : INotifyPropertyChanged
{
private string strDBInstance;
public override string ToString()
{
return strDBInstance;
}
public string DBInstanceName
{
get
{
return strDBInstance;
}
set
{
if (strDBInstance != value)
{
strDBInstance = value;
RaisePropertyChanged("DBInstanceName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You should bind the SelectedItem property of the ComboBox to the SelectedDBInstance property and get rid of the SelectedValuePath:
<ComboBox x:Name="cbxRLFDBInstances" ItemsSource="{Binding DBInstances}"
SelectedItem="{Binding SelectedDBInstance, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="DBInstanceName"/>
The SelectedValuePath property is only used when you want to bind to a source property that is not of the same type as the item in the ItemsSource collection.
To select an item initially you should set the SelectedDBInstance property to an item that is present in the DBInstances collection:
public RLFDatabaseTableViewModel()
{
LoadDBInstances();
LoadDBInfoCommand = new RelayCommand(LoadDBInfo);
SelectedDBInstance = DBInstances[0]; //selected the first item
}
I am developing an application to allow a user to enter their employee details of their company into a database. So far I am experimenting with WPF and trying to implement MVVM within my application while using Entity Framework.
I'm creating a Master-Detail application, and have been researching into how to achieve this using MVVM, as I'm very much new to it all.
One of the ways in which I have tried is by creating a property within my View-Model called SelectedEmployee and then binding it to a List View in my xaml, like so;
public Employee _SelectedEmployee;
public Employee SelectedEmployee
{
get
{
return _SelectedEmployee;
}
set
{
if (_SelectedEmployee == value)
return;
_SelectedEmployee = value;
OnPropertyChanged("SelectedEmployee");
}
}
<ListView HorizontalAlignment="Left" Name="listview" VerticalAlignment="Bottom" ScrollViewer.HorizontalScrollBarVisibility="Visible"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding LoadEmployee}" SelectionMode="Single" SelectedItem="{Binding SelectedEmployee, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="150" Grid.Row="1">
I then have a method that allows the user to update a SelectedItem within the List View. But this is where the problem occurs. When I select an item from the List View, it only updates the first row from the database and not the row I wanted to select.
Here's the method;
public void UpdateEmployee(Employee emp)
{
using (DBEntities context = new DBEntities())
{
emp = context.Employees.Where(e => e.EmployeeID == SelectedEmployee.EmployeeID).FirstOrDefault();
emp.Title = Title;
emp.FirstName = FirstName;
emp.Surname = Surname;
emp.Position = Position;
emp.DateOfBirth = DateOfBirth;
emp.Address = Address;
emp.Country = Country;
emp.Postcode = Postcode;
emp.PhoneNumber = PhoneNumber;
emp.MobileNumber = MobileNumber;
emp.FaxNumber = FaxNumber;
emp.Email = Email;
emp.NINumber = NINumber;
emp.ChargableResource = ChargableResource;
emp.ChargeOutRate = ChargeOutRate;
emp.TimeSheetRequired = TimeSheetRequired;
emp.WorkShift = WorkShift;
emp.BenefitsProvided = BenefitsProvided;
context.Employees.ApplyCurrentValues(emp);
context.SaveChanges();
}
}
I have bound my properties within my view model to the text-boxes within my xaml and then implementing OnPropertyChanged.
I am also using Commands to limit the amount of code-behind as its important for testability and maintainability.
Here is the command method to update;
private ICommand showUpdateCommand;
public ICommand ShowUpdateCommand
{
get
{
if (showUpdateCommand == null)
{
showUpdateCommand = new RelayCommand(this.UpdateFormExecute, this.UpdateFormCanExecute);
}
return showUpdateCommand;
}
}
private bool UpdateFormCanExecute()
{
return !string.IsNullOrEmpty(FirstName) ...
}
private void UpdateFormExecute()
{
UpdateOrganisationTypeDetail();
}
As I am new to MVVM, I'm not quite sure what I am doing wrong so would appreciate some input please :).
Then perhaps the problem is with your updating. I don't really understand what your trying to do with listview, but since it's not working in a simple datagrid this might help. it's not so much an answer as something that I wrote to find your error that happens not to contain your error. please do comment if anything is awry.
public partial class MainWindow : Window
{
private static MainViewModel _mainViewModel = new MainViewModel();
public static ObservableCollection<Employee> staff = new ObservableCollection<Employee>();
public MainWindow()
{
InitializeComponent();
this.DataContext = _mainViewModel;
dataGrid1.DataContext = staff;
listview.DataContext = staff;
Employee Employee1 = new Employee();
Employee1.name = "Jeff";
staff.Add(Employee1);
Employee Employee2 = new Employee();
Employee2.name = "Jefferson";
staff.Add(Employee2);
Employee2.name = "Tim";
}
}
public class Employee
{
public string name { get; set; }
}
public class MainViewModel : INotifyPropertyChanged
{
private string _SelectedEmployee;
public string SelectedEmployee
{
get { return _SelectedEmployee; }
set
{
_SelectedEmployee = value;
NotifyPropertyChanged("SelectedEmployee");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Im having a real headache binding my items to pushpins on a silverlight bing map.
Ive spent all day trying to get my collection sorted and now just cant get the pushpins to show up.
The items appear to be there as when you do a breakpoint on the last line as per the image below, all 143 items are there in _PushPins:
Any help welcome. many thanks.
Here is the code:
namespace observable_collection_test
{
public partial class Map : PhoneApplicationPage
{
public Map()
{
InitializeComponent();
GetItems();
}
private ObservableCollection<SItem2> pushPins;
public ObservableCollection<SItem2> PushPins
{
get { return this.pushPins; }
set
{
this.pushPins = value;
this.OnPropertyChanged("PushPins");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void GetItems()
{
var document = XDocument.Load("ListSmall.xml");
if (document.Root == null)
return;
var xmlns = XNamespace.Get("http://www.blah");
var events = from ev in document.Descendants("item")
select new
{
Latitude = Convert.ToDouble(ev.Element(xmlns + "Point").Element(xmlns + "lat").Value),
Longitude = Convert.ToDouble(ev.Element(xmlns + "Point").Element(xmlns + "long").Value),
};
this.PushPins = new ObservableCollection<SItem2>();
foreach (var ev in events)
{
var pushPin = new SItem2(ev.Latitude, ev.Longitude);
//Location = new GeoCoordinate(ev.Latitude, ev.Longitude )
this.PushPins.Add(pushPin);
}
}
Other class:
namespace observable_collection_test
{
public class SItem2
{
public double Latitude
{ get; set; }
public double Longitude
{ get; set; }
public SItem2(double Latitude, double Longitude)
{
this.Latitude = Latitude;
this.Longitude = Longitude;
}
public Location Location { get; set; }
}
}
XAML:
<my:Map ZoomBarVisibility="Visible" ZoomLevel="10" CredentialsProvider="xxxxx" Height="508" HorizontalAlignment="Left" Margin="0,22,0,0" Name="map1" VerticalAlignment="Top" Width="456" ScaleVisibility="Visible">
<my:MapItemsControl ItemsSource="{Binding PushPins}" >
<my:MapItemsControl.ItemTemplate>
<DataTemplate>
<my:Pushpin Background="Aqua" Location="{Binding Location}" ManipulationCompleted="pin_click">
</my:Pushpin></DataTemplate>
</my:MapItemsControl.ItemTemplate>
</my:MapItemsControl>
</my:Map>
You're trying to bind to a private field, _PushPins, instead of the public property PushPins. Also don't forget to implement INotifyPropertyChanged if you're changing the collection after binding.
I would do two things - firstly, implemented INotifyPropertyChanged and have your PushPins property use a private backing field. In the getter, just return the field value, in the setter, update the field value and invoke the PropertyChanged event.
Then, in your loop, rather than the getItems method (I would use PascalCase for method names) creating a local ObservableCollection, instantiate the PushPins collection and populate it directly.
private ObservableCollectin<SItem2> pushPins;
public ObservableCollection<SItem2> PushPins
{
get { return this.pushPins; }
set
{
this.pushPins = value;
this.OnPropertyChanged("PushPins");
}
}
public void GetItems()
{
...
this.PushPins = new ObservableCollection<SItem2>();
foreach (var ev in events)
{
var pushPin = new SItem2(ev.Latitude, ev.Longitude);
this.PushPins.Add(pushPin);
}
}
Populate the SItem2 Location property in the SItem2 constructor instead using the lat/long, and as Martin says, if you want the UI to update when you change the value of an SItem2 instance in the collection, then implement INotifyPropertyChanged on SItem2's properties also.