I am from VB.Net WinForms comming. Now I wanted to write a small app in WPF, listing some files in a datagridview. I used WPF's DataGrid, created some Columns. And then failed to add my rows.
Please, can you help me to select the right way to get my filenames, state-text and thumbnails added to the DataGrid Row?
In VB.Net WinForms I can add a row like this:
Datagridview1.Rows.add(Myvalue, "RowStateText", "Hello World", MyDate)
In WPF's DataGrid I can add
DataGrid1.Items.Add(New DataGridRow())
But how to fill my DataGridRow?
Private Sub AddFilesAndFolders(ByVal Base As IO.DirectoryInfo, ByRef dgv As DataGrid)
'For Each di As IO.DirectoryInfo In Base.GetDirectories
' Call AddFilesAndFolders(di, dgv)
'Next
Dim item As DataGridRow
For Each fi As IO.FileInfo In Base.GetFiles
item = New DataGridRow'<-- test 1 (row is added but empty)
Dim di As New MyFileInfo'<-- test 2 (my own class with public members, but how to add as row with declared columns?)
di.FileName = fi.FullName
di.FileDate = fi.LastAccessTime
item.Item = fi.FullName
dgv.Items.Add(di)
Next
End Sub
Hi: you should set an ItemsSource instead of adding items manually. If the columns are set up correctly then it will just 'work'!
dbv.ItemsSource = Base.GetFiles
or
dbv.ItemsSource = CreateMyFileInfos(Base.GetFiles)
If you have any more problems, please post back here.
Edit: on second inspection it looks like you may want to be doing it recursively. In which case your AddFilesAndFolders could instead be CreateFilesAndFolders, which would return a collection of FileInfo/MyFileInfo objects, merged with the collections produced by the child folders recursively; then bind the whole list returned from the first call, to the grid.
Hope that helps!
WPF is a mindset change, you need to get away from the Winforms way of thinking.
Ultimately you need to set the ItemsSource to an IEnumerable, preferably a ObservableCollection.
The quickest way to get started would be to put the ObservableCollection as a public property in your code-behind file:
public ObservableCollection<DirectoryInfo> files { get;set; }
Then in the constructor or a Load event on the Window, populate the collection with your data and then add to the Xaml declaration for your DataGrid:
ItemsSource = "{Binding Path=files}"
EDIT:
I tried this out using the DirectoryInfo class, in my code behind I added:
public ObservableCollection<DirectoryInfo> Dir = new ObservableCollection<DirectoryInfo>();
public Window1()
{
InitializeComponent();
Dir.Add(new DirectoryInfo("c:\\"));
Dir.Add(new DirectoryInfo("c:\\temp\\"));
dataGrid1.ItemsSource = Dir;
}
For some reason this was not working using the Databinding via Xaml, but I did not try very hard to get it to work.
Related
I have a page with two parts :
A Listview with itemsource binded with a class, ObservableCollection "Patients" loaded with "Patient" class.
Under the Listview are Textboxes binded with the selecteditem Patient. Everything works without writing any code in the page, except in selection_changed to scroll to selected item.
A second Listview must display the details "Visites" from the selected "Patient".
The application works in a MVVM Framework with a Viewmodel containing the properties for the page.
The problem is to make the relation between the two ListView. I tried first building the second list "Visites" in the NotifyPropertyChanged event :
if (Patient.ID > 0)
{
LoadVisite(Patient.ID); // fill the details list "Visites"
NotifyPropertyChanged("Visites");
}
No delail is shown when "Patient" is selected.
I tried another solution inserting the list of details in the master class "Patient like this :
public Class Patient
...
public ObservableCollection<ClsVisite> Visites
{
get
{
return _visites;
}
set
{
_visites = value;
}
}
// WDABase class to open the database and load data connection
WDABase wd = new WDABase();
wd.LoadListeVisites(ID, _visites); //ID is the relation beween the two tables
}
}
Now I try to create the Listview detail itemsource in the XAML like this :
<ListView Name="ListeVisites" ItemsSource="{Binding Path=Patient.Visites}" SelectedItem="{Binding Visite}">
No details where shown.
The unique solution I found was to add some code in the selection_changed event of the master Listview like this (in this case the listviews are in two different frame) :
private void ListePatients_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ListePatients.ScrollIntoView(ListePatients.SelectedItem);
if(ListePatients.SelectedItem != null)
{
var w1 = ((System.Windows.FrameworkElement)App.Current.MainWindow.Content).Parent;
Frame pageVisite = (w1 as MainWindow).Visit;
var w2 = (System.Windows.FrameworkElement)pageVisite.Content;
ListView Lv = (w2 as Visite).ListeVisites;
Lv.ItemsSource = (ListePatients.SelectedItem as ClsPatient).Visites;
}
}
And it's works but is there another elegant solution to bind the itemsource of the details Listview ?
Thanks for helping me.
Jean-Marie
I found the solution to my problem.
The Reason the details were not displayed is because the frame used for the listview is not common with the master frame.
I discover that when I display the details frame the "Patient" object contains only null. I Don't know why but I read about sharing ViewModel and now I use a common one in App.xaml and I Don't need to specify the itemsource for the details Listview in code. I put in XAML itemsource={Binding Patient.Visites} and everything works.
I have been trying to get my head round binding in WPF forms and am lost in this. I'm trying to do binding in VB .net code. I have the interface:
Implements INotifyPropertyChanged
and if I have a dictionary:
Public Property myDict As New Dictionary(Of String, String)
which I fill up with a key-value bunch of data, then I would like to populate a combobox on a WPF window with the key. So far using the following:
myCombo.ItemsSource = myDict
myCombo.DisplayMemberPath = "Key"
Then I'm trying to link the combobox to a textblock, so that the combobox shows the key and the textblock shows the value. There are lots of these combobox-textbox combinations, so can't really hard code this or have it in xaml. The last thing I tried which does something is the following:
Dim aBinding As New Binding("Value")
aBinding.Source = myDict
myTextBlock.SetBinding(TextBlock.TextProperty, aBinding)
This then shows the textblock to have the first value in the dictionary, but doesn't alter with the combobox. I've tried binding the combobox in the same manner and with a new binding variable, which has the effect of blanking the combobox, but cannot seem to get this working.
Apologies if this is a really, really simple problem, but I'm very new at using binding in WPF, mainly because I've avoided it.
You may create the TextBlock binding like this:
Dim aBinding As New Binding("SelectedItem.Value")
aBinding.Source = myCombo
myTextBlock.SetBinding(TextBlock.TextProperty, aBinding)
I am having a LOT of trouble trying to bind my controls to a data source. I tried binding to XML document. That worked, but lots of issues when I tried to refresh the XML document itself and have it update the UI.
My newest try is to bind my controls to a DataView, which seems simple. I have a sample app I found here on StackOverflow, which does this:
public MainWindow()
{
InitializeComponent();
DataTable dataTable = GetTable();
Binding dataTableBinding = new Binding();
dataTableBinding.Source = dataTable;
dataTableBinding.Path = new PropertyPath("Rows[0][MyTextColumn]");
txtMyTextColumnDataTable.SetBinding(TextBox.TextProperty, dataTableBinding);
DataView dataView = dataTable.DefaultView;
Binding dataViewBinding = new Binding();
dataViewBinding.Source = dataView;
dataViewBinding.Path = new PropertyPath("[0][MyTextColumn]");
txtMyTextColumnDataView.SetBinding(TextBox.TextProperty, dataViewBinding);
}
This works perfectly, right out of the box. I added a button whose code updates the value in the data table, and the textbox immediately reflects the new value when I click that button.
I tried this in my VB.Net project, like this:
dim plcData As DataTable = GetTable()
dim plcView As DataView = plcData.DefaultView
dim plcBinding As Binding = New Binding
plcBinding.Source = plcView
plcBinding.Path = New PropertyPath("(0)(conveyor_plc_data_Main_FeedCarousel_caroAngle)")
Me.tb.SetBinding(TextBlock.TextProperty, plcBinding)
And it doesn't work. It will not update my UI control.
In both cases, GetTable builds a 1-row DataTable with sample data. In my VB project, tb is a TextBlock on my MainWindow.
In the VB project, I can interrupt my code and query the particular data column in the Immediate window, and the proper value is there. It just won't update into my control.
This seems like a very simple thing to do. I am quite new to WPF, and can't see what is wrong with my code. Eventually I would like to define the binding in my XAML, but can't figure out how to do this. At this point, a code-behind setting of the binding would be ok. I will have many controls to be bound to many data columns.
Can anybody tell me what obvious thing I'm missing here?
According to the documentation, the syntax for the PropertyPath class only accepts C#-style indexers.
Single Indexer on the Immediate Object as Data Context:
<Binding Path="[key]" .../>
The class has no way to change its syntax based on the calling language.
EDIT
To set the binding in XAML when the DataView is created in the code-behind, expose the view as a property:
public static readonly DependencyProperty plcViewProperty
= DependencyProperty.Register("plcView", typeof(System.Data.DataView),
typeof(MainWindow), new PropertyMetadata(null));
public System.Data.DataView plcView
{
get { return (System.Data.DataView)GetValue(plcViewProperty); }
set { SetValue(plcViewProperty, value); }
}
private void MainWindow_Initialized(object sender, EventArgs eventArgs)
{
plcView = GetTable().DefaultView;
}
Then in your XAML:
<Window x:Name="TheWindow" ...>
...
Text="{Binding ElementName=TheWindow,
Path=plcView[0][conveyor_plc_data_Main_FeedCarousel_caroAngle]}"
I am using a Dim All_PriceLists As System.Collections.ObjectModel.ObservableCollection(Of BSPLib.PriceLists.PriceListPrime) where PriceListPrime implements Inotify for all properties in it.
I bound the All_PriceList to a datagrid as DataGrid1.ItemsSource = All_PriceLists but when I do All_PriceLists=Getall() where Getall reads and gets the data from the DB, the datagrid is not updating.
It updates only when I hack it this way:
DataGrid1.ItemsSource = Nothing
DataGrid1.ItemsSource = All_PriceLists
Could you please tell me where I have gone wrong or what I should implement. Thank you.
You have several solutions to your problem
Update the ItemsSource directly (instead of replacing the local member variable)
DataGrid1.ItemsSource = new ObservableCollection(Of PriceListPrime)(GetAll())
Update the ObservableCollection (as mentioned in another answer)
All_PriceList.Clear();
For Each item in Getall()
All_PriceList.Add(item)
Next
Set your DataContext to a view model and bind to a property of the view model
Dim vm as new MyViewModel()
DataContext = vm
vm.Items = new ObservableCollection(Of PriceListPrime)(GetAll())
The view model will implement INotifyPropertyChanged and raised the PropertyChanged event when the Items property is changed. In the Xaml your DataGrid's ItemsSource will bind to the Items property.
The problem is that you are not updating the collection, you are replacing it, which is different.
The datagrid remains bound to the old list and the updated data is stored in a new unbound collection. So, you are not hacking a solution, your are binding the datagrid to the new collection, which is correct.
If you want a more automatic solution, you should bind your datagrid to a dataset/datatable, which is totally different code.
You should update ObservableCollection instead of creating new one if you want the app to react on your changes.
So, clear All_PriceList collection and add new items into it. Example:
All_PriceList.Clear();
For Each item in Getall()
All_PriceList.Add(item)
Next
ObservableCollection doesn't support AddRange, so you have to add items one by one or implement INotifyCollectionChanged in your own collection.
I'm using wpf drag and drop databinding to datasets. I generate a new datatable row using the AddNew method of the BindingListCollectionView. I set values on the new row and call CommitNew on the BindingListCollectionView. I expect to see my assigned values in the bound controls but they are all blank. If I save the changes to the database the controls are then updated, but I want the user to see my assigned values before calling UpdateAll on the TableAdapterManager.
Background:
I've created a strongly typed dataset in a project separate from my wpf application. My wpf app references the dataset application. I added an object datasource in the wpf app pointing to the typed dataset. I dragged fields/controls from the datasources window to the wpf designer window. The generated xaml includes a Window.Resources section with a CollectionViewSource accurately bound to my dataset and datatable. This CollectionViewSource is the DataContext for the controls that I dragged to the design surface. All of the controls use TwoWay databinding.
When the window loads I grab a reference to the xaml's CollectionViewSource (using FindResource). I then grab a reference to the view property of the CollectionViewSource and cast it to a BindingListCollectionView. Now I use the AddNew method of the BindingListCollectionView to generate a new row (which AddNew returns as an object). I cast the object to a DataRowView and access it's Row property. I then cast the row to a strongly typed datatable row (generated by the DataSet designer). Now I assign values to some of the datatable row columns. I call CommitNew on the BindingListCollectionView. Finally I call MoveCurrentToFirst on the CollectionViewSource.
Problem:
Using a watch expression I can see the data is in the SourceCollection of both the CollectionView and the BindingListCollectionView. Can anyone explain why the bound controls do not show the data unless I save the changes to the database?
Code (generated XAML not shown):
Private WithEvents _cvsScanData As System.Windows.Data.CollectionViewSource
Private WithEvents _blcvScanData As System.Windows.Data.BindingListCollectionView
_cvsScanData = CType(Me.FindResource("Dt_tblScanDataViewSource"), System.Windows.Data.CollectionViewSource)
_blcvScanData = CType(_cvsScanData.View, BindingListCollectionView)
Dim newRow As LabDataSet.dt_tblScanDataRow = CType(CType(_blcvScanData.AddNew, System.Data.DataRowView).Row, LabDataSet.dt_tblScanDataRow)
newRow.SampleID = "testSampleID"
newRow.MachineID = "testMachineID"
_blcvScanData.CommitNew()
_cvsScanData.View.MoveCurrentToFirst()
The simple fix is to call the Refresh method of the BindingListCollectionView after calling CommitNew.
_blcvScanData.Refresh()
I stumbled across this answer to my own question via intellisense. If anyone can explain why refresh is necessary I'd appreciate it. I expected the INotifyPropertyChange interface to update the bound controls obviating the need to call refresh.