Here is the class of ChildViewModel:
public class ChildViewModel : Screen
{
private string imie = string.Empty;
private string nazwisko = string.Empty;
private string wiek = string.Empty;
private Person person;
private ObservableCollection<Person> personColl;
private MainViewModel mainView = new MainViewModel();
public ChildViewModel(Person person, ObservableCollection<Person> personColl)
{
this.person = person;
this.personColl = personColl;
this.Wyswietl();
}
public string ImieTxt
{
get => this.imie;
set
{
this.imie = value;
this.NotifyOfPropertyChange(() => this.ImieTxt);
}
}
public string NazwiskoTxt
{
get => this.nazwisko;
set
{
this.nazwisko = value;
this.NotifyOfPropertyChange(() => this.NazwiskoTxt);
}
}
public string WiekTxt
{
get => this.wiek;
set
{
this.wiek = value;
this.NotifyOfPropertyChange(() => this.WiekTxt);
}
}
public void Zmien()
{
this.personColl[mainView.DataGridIndex].Imie = this.ImieTxt;
this.personColl[mainView.DataGridIndex].Nazwisko = this.NazwiskoTxt;
this.personColl[mainView.DataGridIndex].Wiek = this.WiekTxt;
this.TryClose();
}
private void Wyswietl()
{
this.ImieTxt = this.person.Imie;
this.NazwiskoTxt = this.person.Nazwisko;
this.WiekTxt = this.person.Wiek;
}
}
I have no idea how to upload new data from ChildView to dataGrid in MainView, after clicking button "Zmien". In MainView I have dataGrid, where from MainViewModel I'm loading data from the list. After clicking button "Zmien", new data doesn't load in dataGrid.
Maybe you have any idea how to do it?
From my article on Codeproject Guide to WPF DataGrid Formatting Using Bindings:
Connecting a DataGrid with Business Data
Even connecting a DataGrid with the business data is not trivial. Basically, a CollectionViewSource is used to connect the DataGrid with the business data:
The CollectionViewSource does the actual data navigation, sorting, filtering, etc.
<Window.Resources>
<CollectionViewSource x:Key="ItemCollectionViewSource" CollectionViewType="ListCollectionView"/>
</Window.Resources>
<DataGrid
DataContext="{StaticResource ItemCollectionViewSource}"
ItemsSource="{Binding}"
AutoGenerateColumns="False"
CanUserAddRows="False">
//create business data
var itemList = new List<stockitem>();
itemList.Add(new StockItem {Name= "Many items", Quantity=100, IsObsolete=false});
itemList.Add(new StockItem {Name= "Enough items", Quantity=10, IsObsolete=false});
...
//link business data to CollectionViewSource
CollectionViewSource itemCollectionViewSource;
itemCollectionViewSource = (CollectionViewSource)(FindResource("ItemCollectionViewSource"));
itemCollectionViewSource.Source = itemList;
Define a CollectionViewSource in Windows.Resource
The gotcha here is that you must set the CollectionViewType. If you
don't, the GridView will use BindingListCollectionView, which does
not support sorting. Of course, MSDN does not explain this anywhere.
Set the DataContext of the DataGrid to the CollectionViewSource.
In the code behind, find the CollectionViewSource and assign your business data to the Source property
In this article, data gets only read. If the user should be able to edit the data, use an ObservableCollection. However, it is often better to leave the DataGrid readonly, because editing in the DataGrid behaves differently from what one is used from spreadsheet programs. It might be better if the user has to doubleclick on the row he wants to change and open another window just for editing that entity or adding a new one.
Related
I have read loads of posts about this topic, but I cannot for the life of me figure this out, so your help is appreciated as I am losing the will to live!
I am trying to bind a list to a combobox in WPF, here is my code:
ViewModel:
public class ViewModelAddRegion
{
public List<DataAccessLayer.Model.CountryList> CountryList { get; set; }
public object GetCountryList()
{
List<DataAccessLayer.Model.CountryList> CountryList = new List<DataAccessLayer.Model.CountryList>();
CountryList = Data.DatabaseGets.GetAllCountries();
return CountryList;
}
}
So that gets my list. In the backing to my window, the code is:
public AddRegion()
{
var vm = new WineDatabase.ViewModel.ViewModelAddRegion();
var CountryAllList = vm.GetCountryList();
DataContext = CountryAllList;
InitializeComponent();
}
And finally, in my window:
<ComboBox Name="CountryList"
Margin="159,0,-160,0"
Grid.Row="1"
Grid.RowSpan="2"
ItemsSource="{Binding CountryAllList}"
DisplayMemberPath="CountryName"/>
Debugging, my list is populated as expected, but the combobox is forever empty.
Thanks for any assistance at all!
CountryAllList is just a local variable that you can't bind to. See the Data Binding Overview article on MSDN for details.
You should assign the ViewModel instance to the DataContext
var vm = new WineDatabase.ViewModel.ViewModelAddRegion();
vm.CountryList = vm.GetCountryList();
DataContext = vm;
and bind to its CountryList property
<ComboBox ItemsSource="{Binding CountryList}" ... />
Finally, in your GetCountryList method, it doesn't make much sense to assign the return value of Data.DatabaseGets.GetAllCountries() to a local variable. You could instead directly return it from the method.
public List<DataAccessLayer.Model.CountryList> GetCountryList()
{
return Data.DatabaseGets.GetAllCountries();
}
The GetCountryList() method may as well directly assign to the CountryList property
public void GetCountryList()
{
CountryList = Data.DatabaseGets.GetAllCountries();
}
and you could write the initialization code like this:
var vm = new WineDatabase.ViewModel.ViewModelAddRegion();
vm.GetCountryList();
DataContext = vm;
Change AddRegion method to:
public AddRegion()
{
var vm = new WineDatabase.ViewModel.ViewModelAddRegion();
vm.CountryList = vm.GetCountryList();
DataContext = vm;
InitializeComponent();
}
And in ComboBox set ItemsSource="{Binding CountryList}"
I need your help! Following is basically what I have in my main XAML view :
<Button x:Name="button1" Content= "{Binding Customer1, Mode=TwoWay}" Margin="271,52,103,106" Click="button1_Click" />
The code-behind of the main XAML (Code-behind, since it's not a 100% pure MVVM, and a rather hybrid one) goes like this :
public MainWindow()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
DXDialog d = new DXDialog("Information", DialogButtons.OkCancel,true);
d.Content = new PropertyGrid();
d.SizeToContent = System.Windows.SizeToContent.WidthAndHeight;
d.Owner = this;
d.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner;
var result = d.ShowDialog();
if (result == true)
{
}
}
As you can see, I have a Button whose content is bound to a String property in the ViewModel Class. Upon Clicking the button, I'm opening a DXDialog which contains a PropertyGrid with the Properties of the ViewModel class. Let me show you my ViewModel Class below :
public class MyViewModel : ViewModelBase, INotifyPropertyChanged
{
Customer currentCustomer;
protected string _customer1;
public string Customer1 {
get { return this._customer1; }
set { this.SetProperty(ref this._customer1, value, "Customer1"); }
}
public MyViewModel()
{
//Customers = new ObservableCollection<Customer>();
//Customers.Add(new Customer() { Name = "Name1" });
Customer1 = "ABC";
}
}
In the Dialog I'm being able to edit the value of the property but don't yet know how I can save it in a way that it immediately reflects even on the button of the main View {Reflects everywhere it must be bound to, I mean}. I can see the execution coming to the following line in the main code behind
if (result == true)
{
}
But I don't know how to get the edited values and plug them into the right place.
Basically, My requirement is to have multiple controls (Buttons, in this case) bound to multiple instances of a ViewModel class, and then, upon clicking the buttons, I should be able to edit those specific ViewModel instances inside the PropertyGrid of the DXDialogue, and after clicking "Ok", the changes should reflect on the relevant buttons as well.
-Ron
To display ViewModel's properties in the PropertyGrid, assign the ViewModel to its SelectedObject property,and make sure that the ShowProperties option is set to All.
Changes will be reflected in buttons bound to the ViewModel only of you use one and the same ViewModel instance in the main and the dialog windows.
var grid = new PropertyGrid();
grid.SelectedObject = this.DataContext;
grid.ShowProperties = ShowPropertiesMode.All;
d.Content = grid;
I populate my listview like this from my sqlite database
private async void getBowlers()
{
SQLiteAsyncConnection conn = new SQLiteAsyncConnection(BOWLERS_DATABASE);
var query = conn.Table<Bowler>();
var result = await query.ToListAsync();
List<String> names = new List<String>();
foreach (var item in result)
{
names.Add(item.Name);
}
itemListView.ItemsSource = names;
}
when I click on one of the items in the list, how can I get the data associated with the clicked item?
since I am just populating the list with a list of strings I really dont see how I can associate any data with it so is there another way to populate my listview? Even just getting the ID would be fine then I could just query based on the ID
You can bind your ListView to an object, rather than just a string. If you do so, when an item is clicked, you will get back the object. In your case, you can bind to the Bowler object, which I assume has an ID and Name field:
public class Bowler
{
public int Id { get; set; }
public string Name { get; set; }
}
In your XAML, you have to tell the ListView that you want to bind the Name field. Here is a simple ListView that just has a TextBlock for each item:
<ListView x:Name="itemListView" ItemsSource="{Binding}"
IsItemClickEnabled="True" ItemClick="itemListView_ItemClick">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the above, the TextBlock will display the Name field.
To load items into the list, you could change your above method to look something like:
// Unrelated, but generally you want to use "async Task"
// in the method signature. "async void" should only be used by
// event handlers, such as a click event.
private async Task getBowlers()
{
SQLiteAsyncConnection conn = new SQLiteAsyncConnection(BOWLERS_DATABASE);
var query = conn.Table<Bowler>();
var result = await query.ToListAsync();
// set page's data context to bowler collection
this.DataContext = result;
}
Now when an item is clicked, you will get back the related Bowler object:
private void itemListView_ItemClick(object sender, ItemClickEventArgs e)
{
Bowler bowler = (Bowler)e.ClickedItem;
// do something with bowler...
}
More info: Windows 8 Metro App ListView Binding and Editing
I'm trying to create DataGrid in a separate UserControl whose DataContext is a List of T.
In the code behind, I create a List, populate the list, then send it to the constructor for the UserControl on which I have the DataGrid I am trying to populate.
The UserControl class is as follows.
public partial class QuotePreview : UserControl
{
private static SelectionList previewList = new SelectionList();
public SelectionList PreviewList
{
get { return previewList; }
}
public QuotePreview()
{
InitializeComponent();
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
previewList = selectedOptions;
QuotePreviewDataGrid.DataContext = previewList;
}
}
And the Xaml looks like:
<DataGrid Name="QuotePreviewDataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Header="Model Number" Binding="{Binding ModelNumber}"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}"/>
<DataGridTextColumn Header="List Price per Unit" Binding="{Binding Price}"/>
</DataGrid.Columns>
</DataGrid>
I've tried setting the ItemSource as well using
QuotePreviewDataGrid.ItemsSource = PreviewList;
I've also tried setting both the data context and the itemsource as well as refreshing:
QuotePreviewDataGrid.Items.Refresh();
The databinding I have set to listboxes in the rest of my application works perfectly. In the list boxes I have the itemsource set to {Binding} and the ListItems binding set to {Binding Property}. The datacontext for the listboxes set in the code behind.
My datagrid here is setup in the same manner, yet for some reason nothing is being displayed inside the grid.
When I go through the debugger and watch the flow of information, I can see the List of T, SelectionsList being created and passed to the constructor for the user control where the data grid lies. I can see that the DataContext is indeed being set and shows the items in the list, but when I go back to my appication and try to view the data grid, it's blank.
Any help would be greatly appreciated. I've been trying to wrap my mind around this problem for the last day and a half. Thanks!
UPDATE
The SelectionList is setup like:
public class SelectionList : List<Selection>
{
public List<Selection> availableSelections = new List<Selection>();
public List<Selection> AvailableSelections
{
get { return availableSelections; }
}
}
and a Selection is then defined by:
public class Selection : DependencyObject
{
public bool IsChecked { get; set; }
public string ModelNumber { get; set; }
public string Description { get; set; }
public string Price { get; set; }
}
When the application starts, I build a catalog of existing products (Selections). On different tabs, one for each product family, the datacontext for the products list box is initialized with with available products that it grabs from the catalog. Then pending which product a user selects, the available options or child selections associated with that product are populated into the appropriate list boxes, accessories and warranties.
Once a user selects the options they want, a button is clicked to preview the selected items which is supposed to populate the data grid explained above.
I can build the list of selected options, however when I try to set the data context of the data grid, nothing appears. The Lists for available selections are built and set to the appropriate data context the same way I am trying to do it for the data grid, however the data grid doesn't want to display my information.
UPDATE
So after some more debugging, I've narrowed the problem down a bit. The data binding works as it should. I have no real problems there, I don't think. However, the issue I'm running into now is what I believe to be 2 different instances of my User Control, but only the original is being displayed, not the updated copy.
Here's a copy of the class from about with a couple lines I added to help debug the problem.
public partial class QuotePreview : UserControl
{
private SelectionList _selectionList;
private SelectionList temp;
public QuotePreview()
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
}
private void QuotePreview_Loaded(object sender, RoutedEventArgs e)
{
_selectionList.SelectedOptions.Add(
new Selection
{
ModelNumber = "this",
Description = "really",
Price = "sucks"
});
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
temp = selectedOptions;
_selectionList.AddRange(selectedOptions);
QuotePreview_Loaded();
}
private void QuotePreview_Loaded()
{
foreach (var options in temp.SelectedOptions)
{
_selectionList.SelectedOptions.Add(options);
}
QuotePreviewDataGrid.ItemsSource = _selectionList.SelectedOptions;
}
}
The implementation of the default constructor, is called every time the user control / tab, is clicked on. When that happens, _selectionList is set to the data context of the user control, followed by the Loaded Event which adds a line to my data grid.
In another user control where I select the options I want to add to my data grid user control, I click a button that creates a list of the options I want to be added and calls the custom constructor I wrote. Once the constructor finishes, it calls a custom Loaded Event method that I created for shits and giggles, that adds the selected options to my _selectionList.
Now once I click on the data grid user control again, it goes through the whole default process, and adds another default line.
If I go back a tab and say I want these options again and go back to the data grid, it again goes through the default process and adds another default line.
Whats most intriguing though is that I can see both of the selectionLists build since I dont clear the in between processes. I see a list build of the options i want to display and a list build of the default options build...
Oh, also, SelectionList does implement ObservableCollection.
I finally came up with a solution to the problem.
public static class QuotePreview
{
public static ObservableCollection<PurchasableItem> LineItems { get; private set; }
static QuotePreview()
{
LineItems = new ObservableCollection<PurchasableItem>();
}
public static void Add(List<PurchasableItems> selections)
{
foreach (var selection in selections)
{
LineItems.Add(selection);
}
}
public static void Clear()
{
LineItems.Clear();
}
}
public class QuoteTab : TabItem
{
public ObservableCollection<PurchasableItem> PreviewItems { get; private set; }
public QuoteTab()
{
Initialize()
PreviewItems = QuotePreview.LineItems;
DataGrid.ItemSource = PreviewItems
}
}
Try changing:
QuotePreviewDataGrid.DataContext = previewList;
to
this.DataContext = previewList;
My suspicion is that the ItemsSource="{Binding}" in your xaml is overriding the DataContext code in your constructor.
By changing the previewList to be DataContext of the entire UserControl, then the binding of the DataGrid's ItemsSource can correctly evaluate.
On a side note, I would start looking into the use of ObservableCollection<T> and the MVVM design pattern. An issue you might end up with is that your DataGrid doesn't update when the underlying list changes, using the ObservableCollection<T> will fix this.
Using the MVVM design pattern will give you a good separation of your logic and data (in this case your list and how it's loaded) from the physical display (the DataGrid)
I've got DataGrid bound to an ObservableCollection<> in its ViewModel:
<DataGrid ItemsSource="{Binding Path=Data}" SelectedItem="{Binding Path=CurrentItem}" />
ViewModel:
public ObservableCollection<TestModel> Data { get; set; }
private TestModel _currentItem;
public TestModel CurrentItem
{
get { return _currentItem; }
set
{
_currentItem = value;
RaisePropertyChanged("CurrentItem");
}
}
Now what I want is, that the DataGrid will preselect the first Row right on Form-startup. So I put the following in my test-code inside the constructor:
Data = new ObservableCollection<TestModel>
{
new TestModel() { Property1 = Guid.NewGuid().ToString() },
new TestModel() { Property1 = Guid.NewGuid().ToString() },
new TestModel() { Property1 = Guid.NewGuid().ToString() }
};
CurrentItem = Data[0];
The data is displayed but the first row isn't selected by the grid. Even if I set the binding to TwoWay, it won't work.
If I remove the SelectedItem-binding in XAML and add the following in Code-behind, it works well:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var m = this.DataContext as MainViewModel;
grid.SelectedItem = m.CurrentItem;
}
What's happening is that your VM is being assigned to the data context before the window is initialized and therefore never receives the message that the CurrentItem has changed because it was changed before it loaded.
What I do, is pass in the view model into View's constructor and set it after the InitializeComponent() function is called. Because I am using Prism I am using inversion of control (IOC) and Prism knows to input my VM into the constructor. If you are instantiating your view and view model yourself, you can just pass in the view model. I ran into the same issue and this works.
public MyView(IMyVM viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
By the way, in working with MVVM, I see no reason not to pass in the ViewModel into the View because the view is dependent on it anyway. I know some people feel differently but it is either this or you will have to so some type of refresh of the datacontext in the Window_Loaded event.