I'm building a simple mvvm WPF app in VS2010 .NET4.0 with Entity Framework 4. I'm a WPF beginner with only 1 year's programming experience. Trying to bind a datagrid in my XAML to an Entity Framework model. I have an observable collection in my View Model but can't seem to read the data in?
I have an Entity Framework .edmx included in the project which holds a single 'tbCountrys' table with a primary ID.
Here's my model class which just declares some variables/properties related to columns in my table and implements INotifyPropertyChanged:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace Entity_MVVM
{
public class CountrysModel : INotifyPropertyChanged
{
#region variables
private string _CountryId;
private string _ShortName;
private string _LongName;
# endregion
# region Properties
public string CountryId
{
get { return _CountryId; }
set { _CountryId = value;
OnPropertyChanged("CountryId");
}
}
public string ShortName
{
get { return _ShortName; }
set
{
_ShortName = value;
OnPropertyChanged("ShortName");
}
}
public string LongName
{
get { return _LongName; }
set
{
_LongName = value;
OnPropertyChanged("LongName");
}
}
#endregion
# region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
# endregion
}
}
My View Model also implements INotifyPropertyChanged, declares an Observable Collection to store my query results and has a LoadGrid() method which should query my table with an EntityConnection and populate the Observable Collection. This doesn't seem to be working? I am invoking the LoadGrid() method from the constructor of my View Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data.EntityClient;
using System.Data;
using System.Windows;
using System.Collections;
namespace Entity_MVVM
{
public class CountrysViewModel : INotifyPropertyChanged
{
#region Constructor
public CountrysViewModel()
{
LoadGrid();
}
# endregion
# region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
# endregion
# region ObservableCollection
private ObservableCollection<CountrysModel> _CountrysModelObservableList = new ObservableCollection<CountrysModel>();
public ObservableCollection<CountrysModel> CountrysModelObservableList
{
get { return _CountrysModelObservableList; }
set
{
_CountrysModelObservableList = value;
OnPropertyChanged("CountrysModelObservableList");
}
}
# endregion
# region Properties
private CountrysModel _CountrysModelView;
public CountrysModel CountrysModelView
{
get { return _CountrysModelView; }
set
{
_CountrysModelView = value;
OnPropertyChanged("CountrysModel");
}
}
# endregion
# region LoadGrid Method
public void LoadGrid()
{
LDBEntities db = new LDBEntities();
using (var conn = new EntityConnection("name=LDBEntities"))
{
conn.Open();
EntityCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM LDBEntities.tbCountrys";
try
{
EntityDataReader rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.CloseConnection);
_CountrysModelObservableList.Clear();
while (rdr.Read())
{
var cCountryId = rdr["CountryId"].ToString();
var cShortName = rdr["shortName"].ToString();
var cLongName = rdr["longName"].ToString();
_CountrysModelView = new CountrysModel()
{
CountryId = cCountryId,
ShortName = cShortName,
LongName = cLongName
};
_CountrysModelObservableList.Add(_CountrysModelView);
}
}
catch
{
MessageBox.Show(string.Format("Can't read in data!"));
}
}
#endregion
}
}
}
Lastly, my XAML view:
<Window x:Class="Entity_MVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Entity_MVVM"
Title="MainWindow" Height="350" Width="525"
DataContext="{DynamicResource MyViewModel}">
<Window.Resources>
<vm:CountrysViewModel x:Key="MyViewModel"/>
</Window.Resources>
<Grid>
<DataGrid Width="400" Height="127" Name="grdPublications" ItemsSource="{Binding Path=CountrysModelObservableList}">
</DataGrid>
</Grid>
</Window>
When I debug the code, the try block in my VIew Model is not executed and the catch exception is thrown. My while loop to read in the data never runs as it bails out after I declare the EntityDataReader object. Maybe there is something not quite right with my query syntax?
Any help would be most appreciated as I can't find many examples online of using the Entity Framework with MVVM. Thanks
Also, getting this exception in the XAML, can't create an instance of my View Model. The connection string is correct in the App.config so not sure what's causing it...
You connection string looks really wrong.
The code is failing on the EntityConnection constructor
at System.Data.EntityClient.EntityConnection.ctor(String connectionString)
So it is this line :
new EntityConnection("name=LDBEntities")
What sort of connection string is that "name=LDBEntities" ?
I think you have forgotten to get the connection string from your ressource file.
new EntityConnection(SomeResource.LDBEntities)
Related
I'm working on an app written in WPF, the code is written in C#.
I have a question mark icon which when pressed suppose to set content to specific label.
The label content is binding to a property in the view model, lets call it 'NoneLegend'.
I want that property to clear itself after 5 second so I have a utility class that suppose to manage that. Inside that class I wrote an anonymous method that gets any type of property.
My question is how do I set that property to string.empty?
The method looks like this:
public static void EmptyStringAfterXseconds<T>(Expression<Func<T>> property)
{
var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
if (propertyInfo == null)
{
throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
}
else
{
var t = propertyInfo.GetType();
propertyInfo.SetValue(null, "");
}
}
And I'm calling it like that:
NoneLegend = "Bla bla...";
Utils.EmptyStringAfterXseconds(() => NoneLegend);
How about this. I'm a bit worried about the new ResetAfterTime() call. Don't know if the instance is around long enough. It might be collected by the Garbage Collector before the Timer fires. Would have to think about that but it seems to work fine.
A Notifier class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
namespace WpfApp1
{
public class PropertyNotifier : INotifyPropertyChanged
{
public void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
// The view automatically fills the PropertyChanged event
public event PropertyChangedEventHandler PropertyChanged;
}
}
The Viewmodel:
using System.ComponentModel;
using System.Threading;
namespace WpfApp1
{
public class ViewModel : PropertyNotifier
{
public ViewModel(string legend)
{
Legend = legend;
}
private string _Legend;
/// <summary>
/// Here's where the magic happens
/// </summary>
public string Legend
{
get => _Legend;
set
{
_Legend = value;
new ResetAfterTime<string>(this, () => Legend, value);
}
}
}
}
And your enhanced routine:
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
namespace WpfApp1
{
public class ResetAfterTime<T>
where T : class
{
Timer _timer;
PropertyInfo _propertyInfo;
PropertyNotifier _notifier;
int _due;
public ResetAfterTime(PropertyNotifier notifier, Expression<Func<T>> property, T value, int due = 3000)
{
_notifier = notifier;
_propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
_due = due;
if (_propertyInfo == null)
{
throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
}
else
{
if (value != default)
{
StartTimer();
}
}
}
private void StartTimer()
{
_timer = new Timer(MakeDisappear, null, _due, _due);
}
private void StopTimer()
{
_timer.Dispose();
_timer = null;
}
private void MakeDisappear(object state)
{
SetValue(null);
StopTimer();
}
private void SetValue(object value)
{
var t = _propertyInfo.GetType();
_propertyInfo.SetValue(_notifier, value);
_notifier.NotifyPropertyChanged(_propertyInfo.Name);
}
}
}
For the binding in the view to know what has changed you need to notify it about that change. The INotifyProperyChanged interface was designed for that purpose. In the code below in the ViewModel this interface is implemented.
I've created a simple WPF program. Here's the MainWindow:
<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"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<Label x:Name="Legend" Content="{Binding Legend}"></Label>
</StackPanel>
</Grid>
</Window>
And the code behind:
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel("Text should disappear");
}
}
}
And the ViewModel:
using System.ComponentModel;
using System.Threading;
namespace WpfApp1
{
public class ViewModel : INotifyPropertyChanged
{
Timer LegendTimer;
public ViewModel(string legend)
{
Legend = legend;
}
private void StartLegendTimer()
{
LegendTimer = new Timer(MakeDisappear, null, 3000, 3000);
}
private void StopLegendTimer()
{
LegendTimer.Dispose();
LegendTimer = null;
}
private void MakeDisappear(object state)
{
Legend = string.Empty;
StopLegendTimer();
}
private string _Legend;
/// <summary>
/// Here's where the magic happens
/// </summary>
public string Legend
{
get => _Legend;
set
{
// Each time the value is set ...
_Legend = value;
// we notify the view that a value has changed
NotifyPropertyChanged(nameof(Legend));
// If the value is not null or empty
if(!string.IsNullOrWhiteSpace(value))
{
// We start the time
StartLegendTimer();
}
}
}
private void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
// The view automatically fills the PropertyChanged event
public event PropertyChangedEventHandler PropertyChanged;
}
}
So here is what i am trying to do. I want to pass the string techName from the model to the view... and I am not understanding how to do this. I can do it from the ViewModel to the view just fine but I want the data to sit in the Model and get pulled by the ViewModel and pass to the View. so that the data can be used across multiple views. That being said here is what I have
MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void DefaultViewClicked(object sender, RoutedEventArgs e)
{
DataContext = new DefaultViewModel();
}
private void NewCallClicked(object sender, RoutedEventArgs e)
{
DataContext = new NewCallViewModel();
}
}
View:
Text="{Binding model.TechName}
ViewModel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using callFlow.Models;
namespace callFlow.ViewModels
{
public class DefaultViewModel
{
private ObservableCollection<DataModel> model = new ObservableCollection<DataModel>();
public DefaultViewModel() { }
private DataModel _selectedModel;
public DataModel SelectedModel
{
get { return _selectedModel ?? (_selectedModel = new SelectedModel()); }
set { _selectedModel = value; }
}
public void changeSelectedModel(DataModel newSelectedModel)
{
SelectedModel.TechName = newSelectedModel.TechName;
}
}
}
Model:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Drawing;
namespace callFlow.Models
{
public class DataModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string techName ="this is a test";
public DataModel()
{
}
public string TechName
{
get { return techName; }
set { techName = value; OnPropertyChanged(); }
}
protected void OnPropertyChanged([CallerMemberName] string techName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(techName));
}
}
}
Your Datacontext class DefaultViewModel does not have any model property.
So you have to replace your XAML with this:
Text="{Binding SelectedModel.TechName}
I've been reading a few articles the past few days on inheriting classes and creating base classes, which i often do in tools I've written. However I specifically was looking into ways to reduce the redundant code often written in my classes which contain INotifyPropertyChange. Normally my classes look something like this, inheriting the base class of NotifyBase. However I've seen here a there in various scripts people moving some of the Get and Set code into the base class. I wanted to know what things to look out for when doing this? is this bad practice or good practice? Does the example I provided written correctly to do so?
One benefit being the Get and Set are much simpler in the classes which inherit NotifyBase in the new setup.
Current Setup Example
FileItem Class
using System.IO;
namespace WpfApplication1
{
public class FileItem : NotifyBase
{
private string _fullPath;
public string FullPath
{
get { return this._fullPath; }
set
{
this._fullPath = value;
this.NotifyPropertyChanged("FullPath");
}
}
}
}
Base Class 'NotifyBase'
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApplication1
{
public class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Possible New Setup
FileItem Class
using System.IO;
namespace WpfApplication1
{
public class FileItem : NotifyBase
{
public string FullPath
{
get { return Get<string>(); }
set { Set(value); }
}
}
}
Base Class 'NotifyBase'
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Varo.Helper
{
public class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private readonly Dictionary<string, object> _propertyValues;
protected NotifyBase()
{
_propertyValues = new Dictionary<string, object>();
}
protected void Set<T>(T value, [CallerMemberName] string name = "")
{
if (_propertyValues.ContainsKey(name))
{
_propertyValues[name] = value;
NotifyPropertyChanged(name);
}
else
{
_propertyValues.Add(name, value);
NotifyPropertyChanged(name);
}
}
protected T Get<T>([CallerMemberName] string name = "")
{
if (_propertyValues.ContainsKey(name))
{
return (T)_propertyValues[name];
}
return default(T);
}
}
}
Check the PropertyChanged.Fody NuGet
Weaves .NET assembly at compile time so properties declared as
public int Property { get; set; }
Gets compiled as
private int property;
public int Property
{
get
{
return property;
}
set
{
if(property != value)
{
property = value;
if (this.PropertyChanged!= null) PropertyChanged(this, new PropertyChangedEventArgs("Property");
}
}
}
I am trying to implement validation within a WPF application using MVVM, I believe that I have the validation set up correctly but when I test it it doesn't seem to give me a red outline on the text box as many examples I've found online do (Such as https://www.youtube.com/watch?v=OOHDie8BdGI).
For simplicity I have narrowed it down to one criterion which is that the textbox for Forename is not left blank and removed the other properties. The only difference I am aware of between the examples in guides I have been using and my own application is that my Model is held on a server whereas the View and ViewModel are client side, could this be what is causing the issues?
Any help would be greatly appreciated as I've been struggling with this for a few days now, thanks!
RegistrationModel (Server side)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Linq;
using System.Web;
namespace ScrumManagementWCFServices.Services.Registration
{
[DataContract]
public class RegistrationModel : IDataErrorInfo, INotifyPropertyChanged
{
private string forename;
[DataMember]
public string Forename
{
get
{
return forename;
}
set
{
forename = value;
NotifyPropertyChanged("Forename");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region IDataErrorInfo Members
string System.ComponentModel.IDataErrorInfo.Error
{
get { return null; }
}
string System.ComponentModel.IDataErrorInfo.this[string propertyName]
{
get
{
return GetValidationError(propertyName);
}
}
#endregion
#region Validation
static readonly string[] ValidatedProperties = {/* "Email", */"Forename"/*, "Surname", "Password", "ConPassword"*/};
public bool IsValid
{
get
{
foreach (string property in ValidatedProperties)
if (GetValidationError(property) != null)
return false;
return true;
}
}
string GetValidationError(String propertyName)
{
string error = null;
switch (propertyName)
{
case "Forename":
error = validateForename();
break;
}
return error;
}
private string validateForename()
{
if (String.IsNullOrWhiteSpace(Forename))
{
return "Customer name cannot be empty.";
}
return null;
}
}
}
RegistrationPage (View - Client side)
<Page x:Class="ScrumManagementApplication.Pages.LoginAndRegistrationPage.View.RegistrationPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ScrumManagementApplication.Pages.LoginAndRegistrationPage.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300"
Title="RegistrationView" Width="300">
<Page.Resources>
<local:RegistrationViewModel x:Key="DataContext"/>
</Page.Resources>
<Grid Background="#FFC0CAEC" DataContext="{StaticResource DataContext}" Margin="10">
<TextBox Text="{Binding RegistrationModel.Forename, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Page>
RegistrationViewModel (Client side)
using ScrumManagementApplication.WCFRegistrationServiceReference;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.ComponentModel;
using ScrumManagementApplication.Pages.LoginAndRegistrationPage.View;
using System.Windows;
namespace ScrumManagementApplication.Pages.LoginAndRegistrationPage.ViewModel
{
public class RegistrationViewModel : INotifyPropertyChanged
{
public RegistrationViewModel()
{
RegistrationModel = new RegistrationModel()
{
Forename = "Rick"
};
}
public RegistrationModel RegistrationModel
{
get;
set;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Thanks in advance!
You need to expose a wrapper property and implement IDataErrorInfo on the view model or bind to the model directly. Validation will not work with bindings like {Binding RegistrationModel.Forename}
You can simplify things a bit with the System.ComponentModel.DataAnnotations Namespace. For example, if you decorate your Forename property with a RequiredAttribute, you will see your required red error outline on your TextBox when the field is left empty:
[DataMember]
[Required(ErrorMessage="Customer name cannot be empty.")]
public string Forename
{
get
{
return forename;
}
set
{
Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = "Forename" });
forename = value;
NotifyPropertyChanged("Forename");
}
}
You can find out more details from the Using Data Annotations to Customize Data Classes page on MSDN. You could of course refactor this to use a single method that both validates and notifies the
INotifyPropertyChanged interface.
I'm a beginner in wpf. I am following a textbook to learn, the examples are shown but its not working whenever I am writing dynamic binding part even after following each of its instructions strictly thrice.
this is the code behind
using System;
using System.ComponentModel;
namespace KarliCards_GUI
{
[Serializable]
public class GameOptions:INotifyPropertyChanged
{
private bool _playAgainstComputer = false;
public bool PlayAgainstComputer
{
get
{
return _playAgainstComputer;
}
set
{
_playAgainstComputer = value;
OnPropertyChanged("PlayAgainstComputer");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In XAML file I want to dynamically bind IsChecked property of a checkbox by using DataContext which will have an instance of GameOptions.
Below part of code is in XAML file
<CheckBox Content="Play against computer" HorizontalAlignment="Left" Margin="11,33,0,0" VerticalAlignment="Top" Name="playAgainstComputerCheck" IsChecked="{Binding Path=PlayAgainstComputer}" />
and below is the code of csharp file of that XAML
namespace KarliCards_GUI
{
public partial class Options : Window
{
private GameOptions _gameOptions;
public Options()
{
if (_gameOptions == null)
{
if (File.Exists("GameOptions.xml"))
{
using (var stream = File.OpenRead("GameOptions.xml"))
{
var serializer = new XmlSerializer(typeof(GameOptions));
_gameOptions = serializer.Deserialize(stream) as GameOptions;
}
}
else
_gameOptions = new GameOptions();
}
DataContext = _gameOptions;
InitializeComponent();
}
}
}
The problem I am facing is, in the property 'PlayAgainstComputer', in set part if I set the variable(_playAgainstComputer) as 'value' it is always checked in the checkbox.
Might be the case the since the InitializeComponent() method is being called after the DataContext is set.
The default value for IsChecked property of CheckBox is true. I suggest first to call the InitializeComponent() method and then set the DataContext.