**[Solved]**this is my first question asked here, so some mistakes could pass.
In my WPF app I want to bind in "two-way mode" all controls to corresponding properties in special object instance BuildingManagementSystem, which I'd like to set as Window DataContext.
But nothing works (data are not displayed). What is the right way to bind it?
Here is my code:
public partial class MainWindow : Window
{
BuildingManagementSystem bms { get; set; }
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Window1_Loaded);
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
bms = new BuildingManagementSystem();
this.DataContext = bms;
}
public class BuildingManagementSystem
{
public string Name { get; set; }
public readonly FireAlarmSystem FireAlarmSystem;
public BuildingManagementSystem()
{
FireAlarmSystem = new FireAlarmSystem();
}
}
class FireAlarmSystem
{
private int alarmSmokeRate, currentSmokeRate;
public List<PowerConsumer> Devices { get; set; }
public int CurrentSmokeRate
{
get { return currentSmokeRate; }
set { SetField(ref currentSmokeRate, value, () => CurrentSmokeRate); }
}
public FireAlarmSystem()
{
Devices = new List<PowerConsumer>();
}
}
class PowerConsumer
{
public string Name { get; set; }
public double Power { get; set; }
public int Priority { get; set; }
public bool Enabled { get; set; }
}
XAML:
<DataGrid Name="FireAlarmGrid" HorizontalAlignment="Left" Margin="10,51,0,0" CanUserAddRows="True"
CanUserDeleteRows="True" VerticalAlignment="Top" AutoGenerateColumns="False" ItemsSource="{Binding FireAlarmSystem.Devices}" >
<DataGrid.RowValidationRules>
<local:FireValidationRule ValidationStep="UpdatedValue"/>
</DataGrid.RowValidationRules>
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Enabled" Binding="{Binding Enabled}"></DataGridCheckBoxColumn>
<DataGridTextColumn Header="Name" Binding="{Binding Name,ValidatesOnExceptions=True }" >
</DataGridTextColumn>
<DataGridTextColumn Header="Power" Binding="{Binding Power}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<TextBox Name="tbCurrentSmokeRate" Text="{Binding Path=FireAlarmSystem.CurrentSmokeRate, Mode=TwoWay}" VerticalAlignment="Top" Width="70"/>
I personally like to create static instances of the viewmodel as shown here:
<Window.Resources>
<ViewModels:MainWindowVM x:Key="VM"></ViewModels:MainWindowVM>
When I do that then binding is easy because the properites page finds the properties for you in VS.
<StackPanel Grid.Column="0" Margin="5,5,5,0"
DataContext="{Binding Source={StaticResource VM}}">
But remember you need to add the namespace
xmlns:ViewModels="clr-namespace:MyWPF.ViewModels"
xmlns:Views="clr-namespace:MyWPF.Views"
This allow property binding like this:
<Views:UcTitle x:Name="XTitle" ></Views:UcTitle>
<Views:UcLegendTitle x:Name="XLegendTitle"/>
<Views:UcSeriesTitle x:Name="XSeriesTitle" />
<Views:UcSeriesTypes x:Name="XSeriesTypes"/>
And you don't have to type any of the names in...
In your case you are not using a ViewModel but you are setting the data-context correctly. So this can only be either no data to display or improper property bind. Remember for this to work you need three things 1) DataContext 2) Vales and 3) Proper binding by Name... It's the third one that trips up folks a lot when just starting out with the great WPF binding system.
Couple of mistakes out here
Seems like BuildingManagementSystem is intended to be your data-context
In place of BuildingManagementSystem bms { get; set; } write the below code:
BuildingManagementSystem bms =new BuildingManagementSystem ();
You need to implement INotifyPropertyChanged event in your ViewModels to reflect the changes on UI
If you intent is to assign values to each of the underlying child view-models, make use of parameterized constructors to pass and assign values. Simply instantiating the child viewModels in parent will not serve any purpose.
To get MVVM working...
You will need to update your View Model to Implement INotifyPropertyChanged to let wpf know your property changed
Use Observable collections instead of lists, ObservableCollections have the wiring to let Datagrid listview, listboxes know items in the collections have changed
Change the Field to a Property so WPF will see it...
<DataGrid Name="FireAlarmGrid" HorizontalAlignment="Left" Margin="10,51,0,0" CanUserAddRows="True"
CanUserDeleteRows="True" VerticalAlignment="Top" AutoGenerateColumns="False" ItemsSource="{Binding FireAlarmSystem.Devices}" >
<DataGrid.RowValidationRules>
<local:FireValidationRule ValidationStep="UpdatedValue"/>
</DataGrid.RowValidationRules>
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Enabled" Binding="{Binding Enabled,Mode=TwoWay}"></DataGridCheckBoxColumn>
<DataGridTextColumn Header="Name" Binding="{Binding Name,ValidatesOnExceptions=True,Mode=TwoWay }" >
</DataGridTextColumn>
<DataGridTextColumn Header="Power" Binding="{Binding Power,Mode=TwoWay}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<TextBox Name="tbCurrentSmokeRate" Text="{Binding Path=FireAlarmSystem.CurrentSmokeRate, Mode=TwoWay}" VerticalAlignment="Top" Width="70"/>
public class BuildingManagementSystem : INotifyPropertyChanged
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
if (_Name != value)
{
_Name = value;
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
private FireAlarmSystem _fireAlarmSystem;
public FireAlarmSystem FireAlarmSystem { get { return _fireAlarmSystem; } }
public BuildingManagementSystem()
{
_fireAlarmSystem = new FireAlarmSystem();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged = delegate { };
#endregion
}
public class FireAlarmSystem : INotifyPropertyChanged
{
private int alarmSmokeRate, currentSmokeRate;
public ObservableCollection<PowerConsumer> Devices { get; set; }
public int CurrentSmokeRate
{
get { return currentSmokeRate; }
set
{
//SetField(ref currentSmokeRate, value, () => CurrentSmokeRate);
PropertyChanged(this, new PropertyChangedEventArgs("CurrentSmokeRate"));
}
}
public FireAlarmSystem()
{
Devices = new ObservableCollection<PowerConsumer>();
//Create some test data...
Devices.Add(new PowerConsumer() { Name = "One", Enabled = true, Power = 100, Priority = 1 });
Devices.Add(new PowerConsumer() { Name = "two", Enabled = false, Power = 101, Priority = 2 });
Devices.Add(new PowerConsumer() { Name = "three", Enabled = false, Power = 103, Priority = 3 });
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged = delegate { };
#endregion
}
public class PowerConsumer
{
public string Name { get; set; }
public double Power { get; set; }
public int Priority { get; set; }
public bool Enabled { get; set; }
}
Thanks to all, I've found a mistake, it was defining FireAlarmSystem as readonly, it shold be a property.
2 Stuart Smith, you're right with INotifyPropertyChanged, but I have an abstract class-ancestor BuildingSystem, which implements this interface. I've forgot to post it.
Related
I am working on a WPF project using Caliburn Micro. In this app I have a DataGrid, which I populate with data from a SQL Server database using Dapper. Please consider the following code snippets:
ChangesModel.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace PTSRDesktopUI.Models
{
//public class for all changes attributes
public class ChangesModel : INotifyPropertyChanged
{
public int ID { get; set; }
public string Facility { get; set; }
public string Controller { get; set; }
public string ParameterName { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
public DateTime ChangeDate { get; set; }
private bool _validated;
public bool Validated
{
get { return _validated; }
set { _validated= value; NotifyPropertyChanged(); }
}
public DateTime? ValidationDate { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
OverviewViewmodel.cs
using Caliburn.Micro;
using PTSRDesktopUI.Helpers;
using PTSRDesktopUI.Models;
namespace PTSRDesktopUI.ViewModels
{
public class OverviewViewModel : Screen
{
//Create new Bindable Collection variable of type ChangesModel
public BindableCollection<ChangesModel> Changes { get; set; }
public OverviewViewModel()
{
//Create connection to dataAccess class
DataAccess db = new DataAccess();
//get the changes from dataAccess function and store them as a bindabla collection in Changes
Changes = new BindableCollection<ChangesModel>(db.GetChangesOverview());
//Notify for changes
NotifyOfPropertyChange(() => Changes);
}
//Validate_Btn click event
public void Validate()
{
//TODO: Change CheckBox boolean value to true and update DataGrid
}
}
}
OverviewView.xaml
<!--Datagrid Table-->
<DataGrid Grid.Row="1" x:Name="Changes" CanUserAddRows="False" AutoGenerateColumns="False">
<DataGrid.Columns>
<!--..........-->
<!--Some irrelevant code-->
<!--..........-->
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="Validated_CheckBox" IsChecked="{Binding Path=Validated, UpdateSourceTrigger=PropertyChanged}" IsHitTestVisible ="False"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn IsReadOnly="True" Binding="{Binding Path=ValidationDate, TargetNullValue='NaN',
StringFormat='{}{0:dd.MM HH:mm}'}"/>
<DataGridTemplateColumn CellStyle="{StaticResource DataGridCellCentered}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button x:Name="Validate_Btn" cal:Message.Attach="[Event Click] = [Action Validate]"
Visibility="{Binding DataContext.Validated,
Converter={StaticResource BoolToVisConverter}, RelativeSource={RelativeSource AncestorType=DataGridCell}}"
cal:Bind.Model="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGrid}}">Validate</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
What I would like to accomplish is this:
When the user clicks the Validate Button, the boolean value for the CheckBox is set to true, the ValidationDate is set to now and the DataGrid is updated. I will then fire a stored procedure to update the database table as well. Also note that the Button is only visible if the CheckBox is checked. So all I want to know is how would I access the Validated property and ValidationDate in the ViewModel method Validate(). Also, how would I update the DataGrid after I change the values for Validated and ValidationDate, so that if I open another ContentControl, the values don't reset?
Anyone have any ideas? Thanks in advance.
Change the signature of your Validate method in the view model to accept a ChangesModel:
public void Validate(ChangesModel model)
{
model.ChangeDate = DateTime.Now;
}
...and change your XAML markup to this:
<Button x:Name="Validate_Btn"
cal:Message.Attach="[Event Click] = [Action Validate($this)]"
cal:Action.TargetWithoutContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGrid}}"
Visibility="...">Validate</Button>
For the data in the DataGrid to get refreshed, you also need to raise the PropertyChanged event for the ChangeDate property:
private DateTime _changeDate;
public DateTime ChangeDate
{
get { return _changeDate; }
set { _changeDate = value; NotifyPropertyChanged(); }
}
I'm trying to create a datagrid which has a entries which have properties coming from a json. The problem is that a lot of the properties should be dynamically generated based on the information that is in the json.
The first thing I tried was to make a very long list of all possible properties with their own getters & seters. But it's a very long list and it felt like there should be an automated solution for this problem. This is included at the end of Datagrid Entry View Model. So I am able to bind to Number when:
Number = obj.NUMBER;
After that I researched into dynamic types and expandoObjects. I learned that apperently it's not possible to bind WPF to an expandoObject's expanded properties. So I cannot bind to expando.Number
expando.Number = obj.NUMBER
I also tried to follow this example in which helper classes are created to help with the binding, but I can't seem to get it to work. When I run my solution I get an error saying that the property "Numberz" doesn't exist. So I cannot bind to Numberz:
AddProperty("Numberz", typeof(string));
SetPropertyValue("Numberz", obj.NUMBER);
At moment I'm out of ideas, so if anybody here could help, I would be very grateful. Maybe a few last notes. I'm not a very experienced programmer and I'm trying to work through an MVVM-pattern.
Datagrid entry view model
public class DatagridEntryViewModel : ICustomTypeProvider, INotifyPropertyChanged
{
#region Fields
/// <summary>
/// Custom Type provider implementation - delegated through static methods.
/// </summary>
private readonly CustomTypeHelper<DatagridEntryViewModel> _helper = new CustomTypeHelper<DatagridEntryViewModel>();
#endregion
#region Constructor
public DatagridEntryViewModel(ExitCoil model)
{
_helper.PropertyChanged += (s, e) => PropertyChanged(this, e);
dynamic Json = JObject.Parse(model.JsonCoilData);
//CreationDatePost = Json.CREATION_DATE_S_POST;
////Number = Json.NUMBER;
//CreationDatePost = Json.CREATION_DATE_S_POST;
////LeverageOrder = Json.LEVERAGE_ORDER;
//Client = Json.CLIENT;
//Skinned = Json.SKINNED;
dynamic expando = new ExpandoObject();
dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(Json.ToString());
//AddProperty("Numberz", typeof(string));
//SetPropertyValue("Numberz", obj.NUMBER);
//expando.AddItem("Number");
//expando[0] = obj.NUMBER;
//Number = expando.Number;
Model = model;
//var settings = new JsonSerializerSettings();
//settings.Converters.Add(new Models.DynamicJsonConverter());
//var serializer = JsonSerializer.Create(settings);
//JsonReader reader = new JsonTextReader(new StringReader(JsonString));
//var Data = serializer.Deserialize<DynamicObject>(reader);
}
#endregion
#region Model & proxy
private ExitCoil _model;
public ExitCoil Model
{
set { _model = value; }
}
#endregion
#region Public Methods
// Methods to support dynamic properties.
public static void AddProperty(string name)
{
CustomTypeHelper<DatagridEntryViewModel>.AddProperty(name);
}
public static void AddProperty(string name, Type propertyType)
{
CustomTypeHelper<DatagridEntryViewModel>.AddProperty(name, propertyType);
}
public static void AddProperty(string name, Type propertyType, List<Attribute> attributes)
{
CustomTypeHelper<DatagridEntryViewModel>.AddProperty(name, propertyType, attributes);
}
public void SetPropertyValue(string propertyName, object value)
{
_helper.SetPropertyValue(propertyName, value);
}
public object GetPropertyValue(string propertyName)
{
return _helper.GetPropertyValue(propertyName);
}
public PropertyInfo[] GetProperties()
{
return _helper.GetProperties();
}
Type ICustomTypeProvider.GetCustomType()
{
return _helper.GetCustomType();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Properties
//public string Number { get; private set; }
//public string CreationDatePost { get; private set; }
//public string LeverageOrder { get; private set; }
public string Client { get; private set; }
//public int OrderZnTop { get; private set; }
//public int OrderZnBottom { get; private set; }
//public string OrderAspect { get; private set; }
//public string OderSideTrimming { get; private set; }
//public string OrderPassivation { get; private set; }
//public string OrderOilType { get; private set; }
//public string OrderOilQuantity { get; private set; }
#endregion
}
Datagrid view model
public class DatagridViewModel : MainViewModelBase
{
#region Singleton
public static DatagridViewModel Instance = new DatagridViewModel();
#endregion
#region Fields
private ObservableCollection<DatagridEntryViewModel> _coilEntries = new ObservableCollection<DatagridEntryViewModel>();
private readonly IViewManager _viewManager;
private readonly IDockManager _dockManager;
#endregion
#region Properties
public ObservableCollection<DatagridEntryViewModel> CoilEntries
{
get { return _coilEntries; }
set
{
_coilEntries = value;
RaisePropertyChanged(() => CoilEntries);
}
}
public ProductionExitCoilReport Report { get; set; }
#endregion
#region Constructor
public DatagridViewModel()
{
_viewManager = Globals.Container.Resolve<IViewManager>();
_dockManager = Globals.Container.Resolve<IDockManager>();
getReport();
foreach (var Coil in Report.Coils)
{
CoilEntries.Add(new DatagridEntryViewModel(Coil));
}
}
#endregion
#region Methods
public void getReport()
{
var proxy = new Proxy<IReportService>();
proxy.Open();
var LijnId = new LijnIdentificatie { Department = "HDG", Firm = "LIE", LineNumber = "05" };
var Periode = new Period { FromDate = new DateTime(2017 - 11 - 10), FromShift = 1, ToDate = new DateTime(2017 - 11 - 10), ToShift = 1 };
var request = new ProductionReportRequest() { LijnIdentificatie = LijnId, Period = Periode };
Report = proxy.Service.GetProductionExitCoilReport(request);
}
#endregion
}
Datagrid XAML
<DataGrid x:Class="Promo.Presentation.Views.CoilGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/marukp-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Promo.Presentation.Views"
xmlns:components="clr-namespace:Promo.Presentation.Components"
xmlns:Data="clr-namespace:Promo.Presentation.ViewModels"
AutoGenerateColumns="False"
GridLinesVisibility="none"
Background="White"
Height="Auto"
Width="Auto"
MinWidth="500"
MinHeight="500"
DataContext="{x:Static Data:DatagridViewModel.Instance}"
ItemsSource="{Binding CoilEntries}"
IsReadOnly="True"
HeadersVisibility="Column"
Padding="10"
>
<DataGrid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/Colors.xaml"/>
<ResourceDictionary Source="../Styles/Datagrid.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Numberz}" Header="Rolnummer" MaxWidth="125"/>
<DataGridTextColumn Binding="{Binding CreationDatePost}" Header="Post" MaxWidth="200"/>
<DataGridTextColumn Binding="{Binding LeverageOrder}" Header="Leverage order" MaxWidth="200"/>
<DataGridTextColumn Binding="{Binding Client}" Header="Client" MaxWidth="400"/>
<DataGridTextColumn Binding="{Binding OrderZnTop}" Header="Order zinc top" MaxWidth="200"/>
<DataGridTextColumn Binding="{Binding OrderZnBottom}" Header="Order zinc bottom" MaxWidth="200"/>
<DataGridTextColumn Binding="{Binding OrderAspect}" Header="Order aspect" MaxWidth="125"/>
<DataGridCheckBoxColumn Binding="{Binding OrderSideTrimming, Converter={local:StringToBoolConverter}}" Header="Order Side Trimming" MaxWidth="200"/>
<DataGridTextColumn Binding="{Binding OrderPassivation}" Header="Order Passivation" MaxWidth="200"/>
<DataGridCheckBoxColumn Binding="{Binding Skinned, Converter={local:StringToBoolConverter}}" Header="Skinned" MaxWidth="200"/>
</DataGrid.Columns>
</DataGrid>
Part of the Json
{"NUMBER":"G571015110 ","CREATION_DATE_S_POST":"2017-05-11T00:00:00""LEVERAGE_ORDER":"FD72BIGA5W/8","CLIENT":"ARCELORMITTAL BELGIUM SA "}
If you have a json with all of the properties then Visual Studio can generate class for you! no need to create them your self. I worked on a similar scenario in the past, this has been done before.
To paste a class generated from json click in your .cs file where you want to generate the code, then click on Edit in the Visual Studio menu, next select Paste Special and then select Paste JSON As Classes.
This should make it a bit easier for you.
I am working with mvvmLight-framework. I got two usercontrols.
One usercontrol (objectInspector) is for saving data:
<Button Command="{Binding ObjectModel.OkCommand}" />
It is bound to the view-model "objectInspectorViewModel".
The other usercontrol (outliner) is for loading/presenting all of the data
<DataGrid ItemsSource="{Binding Path=ObjectModels, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="0"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" Header="ID"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Name}" Header="Name"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Length}" Header="Length"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Height}" Header="Height"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Width}" Header="Width"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Type}" Header="Type"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
It is bound to the viewmodel "outlinerViewModel".
Loading and saving works fine. But what i want is to refresh the datagrid after saving a new object.
My OutlinerViewModel looks like this:
public class OutlinerViewModel : BaseViewModel
{
public List<ObjectModel> ObjectModels { get; set; }
public OutlinerViewModel()
{
string file = $#"{Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.Parent.FullName}\DataSource\objects.csv";
ObjectModels = ReadFile(file);
}
[...]
}
My ObjectInspectorViewModel looks like this:
public class ObjectInspectorViewModel : BaseViewModel
{
public ObjectModel ObjectModel { get; set; } = new ObjectModel();
}
And this is the method for saving a new object to the ''database'' from the ObjectModel:
public RelayCommand OkCommand { get; private set; }
protected override void InitCommands()
{
base.InitCommands();
OkCommand = new RelayCommand(
() =>
writeToCsv(
$#"{Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.Parent.FullName}\DataSource\objects.csv",
this.ToString()),
() => IsOk);
}
How to update dataGrid after saving data with MvvmLight?
Personnaly, I like to have my view up to date with the data in the database.
So when I save an element, I refresh the datagrid by retrieving the data from there.
This way, you don't have synchronizing issue. If performance is important, you can try to update only the element you saved.
So in your case, i'd just call the "readFile" method after saving an object.
By the way, all your ObjectModel properties should call RaisePropertyChanged in your ViewModel:
Something like this:
private long IdProperty;
public long Id
{
get { return IdProperty; }
set { IdProperty = value; RaisePropertyChanged(() => Id); }
}
private long NameProperty;
public long Name
{
get { return NameProperty; }
set { NameProperty = value; RaisePropertyChanged(() => Name); }
}
This way, all the properties are updated in the view when they're modified. (RaisePropertyChanged comes with the ViewModelBase class)
Change your List<ObjectModel> to an ObservableCollection<ObjectModel>.
It should be that simple.
Update
Also, raise PropertyChanged.
private ObservableCollection<ObjectModel> objectModels;
public ObservableCollection<ObjectModel> ObjectModels
{
get
{
return onjectModels;
}
set
{
objectModels = value;
OnPropertyChanged();
}
}
Assuming your view model implements INotifyPropertyChanged. It could be implemented like this.
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName=null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
I have datagrid, which should allow user add row. But it should add item where one of the field defined by the one of the model properties. Have anybody an idea how to do it.
Code like this:
public class OrderWindowModel : BaseModel
{
public ObservableCollection<GoodM> Goods { get; set; }
public Service Service { get; set; }
}
public class GoodM : BaseModel
{
public Service Service { get; set; }
public List<Good> Goods
{
get{ return Service.GoodsL; }
}
public Good CurrGood { get; set; }
}
And xaml
<custom:DataGrid Margin="5" Grid.Row ="1" CanUserAddRows="True" CanUserDeleteRows="True"
AutoGenerateColumns="False" SnapsToDevicePixels="True" SelectedIndex="0"
CanUserReorderColumns="True" ItemsSource="{Binding Goods}" Grid.ColumnSpan="2">
<custom:DataGrid.Columns>
<custom:DataGridComboBoxColumn Header="Товар" DisplayMemberPath="{Binding Name}"
SelectedItemBinding="{Binding CurrGood}" ItemsSource="{Binding Goods}" Width="*">
</custom:DataGridComboBoxColumn>
</custom:DataGrid.Columns>
</custom:DataGrid>
You can do that using code behind. Hook to InitializingNewItem event in your XAML:
<DataGrid InitializingNewItem="DataGrid_InitializingNewItem"/>
In the handler you will get NewItem added where you can set the value for your field:
private void DataGrid_InitializingNewItem(object sender,
InitializingNewItemEventArgs e)
{
if (e.NewItem != null)
{
((GoodM)newItem).Service = // Set value here;
}
}
I'm using MVVM for my project and I'm trying to bind a table from my database with a DataGrid. But when I run my application datagrid is empty.
MainWindow.xaml.cs:
public MainWindow(){
InitializeComponent();
DataContext = new LecturerListViewModel()
}
MainWindow.xaml:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Source=Lecturers}" >
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Surname" Binding="{Binding Surname}"/>
<DataGridTextColumn Header="Phone" Binding="{Binding Phone_Number}" />
</DataGrid.Columns>
</DataGrid>
LecturerListViewModel.cs:
public class LecturerListViewModel : ViewModelBase<LecturerListViewModel>
{
public ObservableCollection<Lecturer> Lecturers;
private readonly DataAccess _dataAccess = new DataAccess();
public LecturerListViewModel()
{
Lecturers = GetAllLecturers();
}
and ViewModelBase implements INotifyPropertyChanged.
Lecturer.cs
public class Lecturer
{
public Lecturer(){}
public int Id_Lecturer { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Phone_Number { get; set; }
What did I do wrong? I checked it with debuger and DataContext contains all lecturers, but ther aren't shown in datagrid.
You have an error in binding. Try this:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Lecturers}" >
Code-behind:
private ObservableCollection<Lecturer> _lecturers = new ObservableCollection<Lecturer>();
public ObservableCollection<Lecturer> Lecturers
{
get { return _lecturers; }
set { _lecturers = value; }
}
Here is simple example code (LecturerSimpleBinding.zip).
Here we go
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=Lecturers}" >
Then
private ObservableCollection<Lecturer> lecturers;
public ObservableCollection<Lecturer> Lecturers
{
get { return lecturers; }
set
{
lecturers = value;
this.NotifyPropertyChanged("Lecturers");
}
}
Sayed Saad above is correct. I see two potential problems with your setup, both of which Sayed resolves.
The example posted in the question doen not implement INotifyPropertyChanged
The CLR property being bound to must be a PUBLIC PROPERTY. Fields will not work, as databindindg works via reflection.
Lecturers is a field, but data binding works with properties only. Try declaring Lecturers like:
public ObservableCollection<Lecturer> Lecturers { get; set; }
MainWindow.xaml.cs: OK
MainWindow.xaml: OK
LecturerListViewModel.cs: OK - assuming that GetAllLecturers() method returns an ObservableCollection of Lecturer.
Lecturer.cs:
public class Lecturer : INotifyPropertyChanged
{
//public Lecturer(){} <- not necessary
private int _id;
public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
// continue doing the above property change to all the properties you want your UI to notice their changes.
...
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Check this answer:
Adding INotifyPropertyChanged to Model?