I am new to MVVM and databinding and I am having some trouble binding a gridview to a datatable dynamically. I am able to get the column headers to bind, but no data is being displayed in the grid itself.
My model simply returns a data table as the result of a SQL string passed to it.
My viewmodel just wraps the datatable and gets bound to the view.
Right now I am just trying to display the data by populating the gridview from the main window, but only the headers are being displayed.
I know there is data in the model.Results datatable though.
My viewmodel:
public class ResultsViewModel
{
private DataTable _dt;
public ResultsViewModel()
{
DataSource _ds = new DataSource();
_dt = _ds.Execute("select * from tbl_users");
}
public DataTable Results
{
get { return _dt; }
set { _dt = value; }
}
}
My code to populate the gridview from the mainwindow:
public MainWindow()
{
InitializeComponent();
ResultsView view = new ResultsView();
ResultsViewModel model = new ResultsViewModel();
GridView Grid = new GridView();
foreach (DataColumn col in model.Results.Columns)
{
Grid.Columns.Add(new GridViewColumn
{
Header = col.ColumnName,
DisplayMemberBinding = new Binding(col.ColumnName)
});
}
view._listView.View = Grid;
view.DataContext = model;
view.SetBinding(ListView.ItemsSourceProperty, new Binding());
_placeholder.Content = view;
}
The ResultsView xaml:
<UserControl x:Class="InDevReporting.Views.ResultsView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ListView x:Name="_listView" />
</Grid>
Try setting your data context to model.Results.
ie change this line:
view.DataContext = model;
to this:
view.DataContext = model.Results;
Generally you would create a dependency property on your view model and specify the binding in the XAML. The grid should be clever enough to figure out what columns to draw:
<ListView ItemsSource="{Binding Results}" />
public MainWindow()
{
InitializeComponent();
// your code to instance and populate model
this.DataContext = model;
}
public class ResultsViewModel : DependencyObject
{
public static readonly DependencyProperty ResultsProperty = DependencyProperty.Register("Results", typeof(DataTable) , typeof(ResultsViewModel));
public DataTable Results
{
get { (DataTable)GetValue(ResultsProperty); }
set { SetValue(ResultsProperty, value); }
}
}
I've tapped this out from memory, so apologies if the code isn't exactly right. The easiest way to declare a new dependency property is to use the propdp code snippet. It's a lot of syntax to memorize.
Related
I would like to implement a DataGrid that displays data of objects with the same class. A list, which accepts generic type of class, of objects called objectsList and a list of string of properties called propertiesToDisplay are provided so that the DataGrid can choose which properties of the objects to be shown, according to propertiesToDisplay. How can I implement it?
MainWindow
<Window x:Class="SomeProject.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:SomeProject"
mc:Ignorable="d"
Title="MainWindow" MinHeight="450" MinWidth="700">
<Grid>
<DataGrid ItemsSource="{Binding ObjectsList}">
<!-- Anything Else? -->
</DataGrid>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// How is the binding?
}
}
ViewModel
public class ViewModel<T>
{
private IList<T> _objectsList;
public IList<T> ObjectsList
{
get
{
return _objectsList;
}
set
{
_objectsList = value;
}
}
private IList<string> _propertiesToDisplay;
public IList<string> PropertiesToDisplay
{
get
{
return _propertiesToDisplay;
}
set
{
_propertiesToDisplay= value;
}
}
// Constructor
public ViewModel(IList<T> objectsList, IList<string> propertiesToDisplay)
{
// Please help me to correct the following code
PropertyInfo[] propertyInfos = null;
foreach (var propertyName in propertiesToDisplay)
{
propertyInfos = typeof(T).GetProperties();
var names = propertyInfos.Select(x => x.Name);
if (!names.Contains(propertyName))
{
throw new ArgumentException("");
}
}
try
{
ObjectsList = objectsList;
foreach (var obj in objectsList)
{
foreach (var propertyName in propertiesToDisplay)
{
PropertiesToDisplay.Add(propertyInfos.Where(x => x.Name.Equals(propertyName)).FirstOrDefault().GetValue(obj, null).ToString());
}
}
}
catch (Exception ex)
{
}
}
}
You could create the columns dynamically in the view based on the PropertiesToDisplay source property. Something like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel<YourClass> viewModel = new ViewModel<YourClass>(..., ...);
foreach (string column in viewModel.PropertiesToDisplay)
dataGrid.Columns.Add(new DataGridTextColumn() { Header = column, Binding = new Binding(column) });
dataGrid.AutoGenerateColumns = false;
DataContext = viewModel;
}
}
XAML:
<DataGrid x:Name="dataGrid" ItemsSource="{Binding ObjectsList}" />
I have a UserControl 'UserControlA' with ViewModel 'ViewModelA'.
'UserControlA' has 'UserControlB', and 'UserControlB' has 'ViewModelB'.
When I bind a DependencyProperty in 'UserControlA' with 'ViewModelA' property,
there is none of setter fired.
Belows are code,
ViewA.xaml
<UserControl
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:vm="clr-namespace:MyTest.ViewModel
xmlns:custom="clr-namespace:MyTest.Views
x:Name="userControl" x:Class="MyTest.Views.UserControlA"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500">
<UserControl.DataContext>
<vm:UserViewModel x:Name="uvModel"/>
</UserControl.DataContext>
<Grid>
<custom:UserControlB></custom:UserControlB>
ViewA.cs
public partial class UserView : UserControl, IUserView
{
static DependencyProperty UserTypeProperty = DependencyProperty.Register("UserType", typeof(UserType), typeof(UserView), new PropertyMetadata(UserType.None));
public UserType UserType { get { return (UserType)GetValue(UserTypeProperty); } set { SetValue(UserTypeProperty, value); } }
public ViewA()
{
InitializeComponent();
Binding typeBinding = new Binding();
typeBinding.Source = this.DataContext;
typeBinding.Path = new PropertyPath("User.UserType");
typeBinding.Mode = BindingMode.OneWayToSource;
this.SetBinding(UserTypeProperty, typeBinding);
}
ViewModelA.cs
public class ViewModelA : ViewModelBase
{
User user = new User();
public User User
{
get { return this.user; }
set
{
this.user = value;
RaisePropertyChanged(() => User);
}
}
Please help me out from this problem.
The line
typeBinding.Source = this.DataContext;
is redundant, because the DataContext is implicitly used as source object of the Binding.
However, during the execution of the UserControl's constructor the DataContext property is not yet set (i.e. it is null), so you are effectively setting the Binding's Source property to null. Just remove that line, or write
SetBinding(UserTypeProperty, new Binding
{
Path = new PropertyPath("User.UserType"),
Mode = BindingMode.OneWayToSource
});
I have a DataGrid which is bound to an ObservableCollection ProductsFound
which is exposed as a property in my ViewModel.
By typing text in a TextBox, products contained in the model that have the Code property that contains the text inserted in the TextBox are added to ProductsFound.
I found out that if the DataGrid is contained in any control such as a StackPanel or a TabItem, the Window (the program) stops responding when I try to type text into the TextBox; while if the DataGrid isn't contained in any control, everything runs normally.
Here's the code for the window:
public class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// This method just fill the dataset I pass to the model's contructor in the next line.
Init();
ProductsModel model = new ProductsModel(dataSet);
searchViewModel = new ProductsSearchViewModel(model);
DataContext = searchViewModel;
}
private ProductsSearchViewModel searchViewModel;
// This handler supports the binding between the TextBox and the MatchText property of the View Model.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
}
}
And here's my ViewModel:
public class ProductsSearchViewModel : Notifier, IProductsSearchViewModel
{
public ProductsSearchViewModel(IProductsModel inModel)
{
model = inModel;
productsFound = new ObservableCollection<ProductViewModel>();
}
private string matchText;
private IProductsModel model;
private ObservableCollection<ProductViewModel> productsFound;
// This is a helper method that search for the products in the model and adds them to ProductsFound.
private void Search(string text)
{
Results.Clear();
foreach (Product product in model.Products)
{
if (product.Code.ToLower().Contains(text.ToLower()))
Results.Add(new ProductViewModel(product));
}
}
public string MatchText
{
get { return matchText; }
// This setter is meant to be executed every time the Text property of the TextBox is changed.
set
{
if ((value != matchText) && (value != ""))
{
matchText = value;
// This raises INotifyPropertyChanged.PropertyChaged.
NotifyPropertyChanged("MatchText");
Search(value);
}
}
}
public ObservableCollection<ProductViewModel> ProductsFound
{
get
{
return productsFound;
}
set
{
productsFound = value;
NotifyPropertyChanged("Results");
}
}
}
Here's the XAML:
<Window x:Class="MyNameSpace.UI.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>
<StackPanel>
<TextBox Text="{Binding MatchText, Mode=TwoWay}" TextChanged="TextBox_TextChanged" />
<DataGrid x:Name="grid1" ItemsSource="{Binding Results}" >
</StackPanel>
</Grid>
With that StackPanel the program stops responding when I try to type text in the Textbox and no item is added to the DataGrid; but if i remove it everything runs ok.
What could the problem be? Am I missing something in how the WPF binding system works?
Is my view model coded wrong?
Thanks in advance.
Putting that StackPanel there prevents the DataGrid from acquiring a specific Height, thus it just expands down to infinity, and that breaks UI Virtualization.
Remove the StackPanel from there and use a non-infinite container, such as Grid or DockPanel.
What I am trying to do is simply have a combobox populate with data from an sqlite table. While I have done this with code methods, I wold really like to do this in what I can see is the better WPF way to do things.
From what I understand the flow should go something like this:
I should have a class that holds the data, I made a quick class which the default constructor is to connect to the database and dump it's results to a list like so:
internal class mainmenusql
{
private List<string> _Jobs;
public mainmenusql()
{
SQLiteConnection conn = new SQLiteConnection();
conn.ConnectionString = "Data Source=C:\\Users\\user\\Documents\\db.sqlite;Version=3";
try
{
conn.Open();
SQLiteDataReader reader;
SQLiteCommand command = new SQLiteCommand(conn);
command.CommandType = CommandType.Text;
command.CommandText = "SELECT * FROM Tasks";
reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
_Jobs.Add(reader.GetValue(0).ToString());
}
}
else
{
MessageBox.Show("No records");
}
}
catch (Exception err)
{
MessageBox.Show(err.Message);
}
finally
{
conn.Close();
}
}
}
Having some errors with the list "Object reference not set to an instance of an object".
But anyways, the next step should be to set the DataContext of the form to this object right?
public MainWindow()
{
DataContext = new mainmenusql();
InitializeComponent();
}
And finally the combobox should have a binding right?
<Window x:Class="sqliteDatacontext.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>
<ComboBox DataContext="{Binding Path=_Jobs}" HorizontalAlignment="Left" Margin="141,124,0,0" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
What am I doing wrong here?
For binding to something of a datacontext, it needs to be exposed via public getter / setter...
public class mainmenusql
{
public List<string> _Jobs
{ get ; protected set; }
public mainmenusql()
{
_Jobs = new List<string>();
// rest of populating your data
}
}
The binding in your window control is the ItemsSource
<ComboBox ItemsSource="{Binding Path=_Jobs}" />
The "DataContext" is applied to the entire window... From that being the basis, any of your controls can have their element "bound" to almost anything "publicly" available ON your data context... in this case, a ComboBox list of choices comes from its "ItemSource" property... So you want the ITEMSOURCE pointing to your _Jobs.
I am just new to WPF and I am having problems displaying my record. It seems that my records are "shy" when it comes to displaying them, even though I have all my records already.
Code for my App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window1 window = new Window1();
var viewModel = new Window1ViewModel();
window.PHGrid.ItemsSource = viewModel.ViewModels;
window.Show();
}
Code for Window1ViewModel:
public class Window1ViewModel : ViewModelBase
{
private readonly DAPHContrib _contribRepository;
private ObservableCollection<ViewModelBase> _viewModelBases;
public ObservableCollection<ViewModelBase> ViewModels
{
get
{
if (_viewModelBases == null)
{
_viewModelBases = new ObservableCollection<ViewModelBase>();
}
return _viewModelBases;
}
}
public Window1ViewModel()
{
_contribRepository = new DAPHContrib();
//Create instance of our view model to add it in our collection
PHContribViewModel viewModel = new PHContribViewModel(_contribRepository);
ViewModels.Add(viewModel);
}
}
Here's my Window1.xaml UPDATED:
<Window x:Class="Wabby_App.Views.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Wabby_App.ViewModels"
xmlns:v="clr-namespace:Wabby_App.Views"
Title="Utos ng mahal ko"
Height="300"
Width="300">
<Grid>
<DataGrid
AutoGenerateColumns="True"
Height="200"
HorizontalAlignment="Center"
Name="PHGrid"
VerticalAlignment="Center"
Width="200"
ItemsSource="{Binding ViewModels}"/>
</Grid>
Output:
Hope you can help me with this.
based on your comments you have view models collection (ObservableCollection<ViewModelBase>)
and inside each of these view model base instances (PHContribViewModel) you have another collection ObservableCollection<PHContrib_Entity>.
Hence you have two levels of nested collections and one datagrid to map. This wont work as it is. For this you would need to flatten this 2 level hierarchy of collections into one list of type ObservableCollection<PHContrib_Entity>.
Use LINQ to do that...
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
Window1 window = new Window1();
var viewModel = new Window1ViewModel();
window.PHGrid.ItemsSource
= viewModel.ViewModels.SelectMany(vm => vm.PHContribEntities).ToList();
window.Show();
}
Let me know if this helps...
Your View (Window1) is not binding to ViewModel, it's just setting the control's ItemsSource to a property of the ViewModel which is an incorrect way to implement MVVM. What you need to do is set DataContext of Window1 to instance of ViewModel (Bind View to ViewModel). So, you need to update your code in the OnStartup method.
from
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window1 window = new Window1();
var viewModel = new Window1ViewModel();
window.PHGrid.ItemsSource = viewModel.ViewModels;
window.Show();
}
to
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window1 window = new Window1();
var viewModel = new Window1ViewModel();
window.DataContext = viewModel;
window.Show();
}
Update
You also need to set ItemsSource property of datagrid to property in ViewModel
<DataGrid ItemsSource={Binding ViewModels} ..