VB .Net WPF Binding 2 controls to dictionary - wpf

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)

Related

Silverlight: How can I set a converter on a ComboBox ItemsSource in the code-behind?

I have a combobox that is populated at runtime with values from a loadoperation (I'm using RIA services)
cboSite.ItemsSource = lo.Entities;
However, I want to be able to add a null item to the top of the list shown in the combobox, so following the example given here:
http://clr-namespace.com/post/SilverlightWPF-ComboBox-with-Empty-Item-allows-user-to-go-back-to-no-selection.aspx
I am trying to use a converter to insert the item at the top of the list. However, the problem I have is that I can't seem to work out how to specify the converter in the code behind!
Any ideas how to achieve this?
If you are willing to assign ItemsSource from the code-behind you can convert your Entities in the same very place. Something like this:
var converter = new AddEmptyItemConverter();
var converted = (IEnumerable<Entity>)converter.Convert(lo.Entities,
typeof(IEnumerable<Entity>),
null,
null);
cboSite.ItemsSource = converted;
That Entity should be the type of Entities collection element.

Problem with filtering DataGrid

I have bound my DataGrid to a DataTable and only few of the details are displayed in the grid. When I wanted to filter the DataGrid I created a View with my DataGrid's ItemsSource.
Code:
Dim myView As ICollectionView = CollectionViewSource.GetDefaultView(MyDGrid.ItemsSource)
myView.Filter = New Predicate(Of Object)(AddressOf filterFunc1)
Now When I do the search, the non-displayed fields are also included in the search.
Public Function filterFunc1(ByVal obj As Object) As Boolean
Dim filStr As String = "*" & TextBox1.Text & "*"
For Each item As String In obj.Row.ItemArray
**If item.ToLower Like filStr.ToLower Then**
Return True
End If
Next
Return False
End Function
Also I Have ComboBox fields in the DataGrid which are loaded separately from other DataTable's. Now I cant Include them in the search.
A screenshot from my App:
So how do I make a search that includes only the Text from Displayed part.
EDIT: Also how do I skip searching the null valued fileds? 'cause thats causing an exception in my case.
Well then...
Your question is pretty disjointed and I can't understand all of it - maybe that's why you didn't get an answer so far. Skipping null fields is simply a matter of adding a new condition in filterFunc1 - if Convert.IsDBNull(item) then continue for (assuming item is a field in a DataRow, of course).
However, this programming style is pretty foggy and I'd recommend at the very least being more clear on which columns you filter, and the types of objects in the columns. A much better approach would be to map the data you're getting from the database to actual objects in your application - that allows for more type-safe programming. I think the main problem here is that nobody can really tell what's going on there from a few lines of code because nobody can make any assumptions about what kind of objects are there.
About the items in the ComboBox, no idea what kind of difficulties you're having, you might want to clear that up a bit.
you could maintain, instead of simply strings, structures containing both captions and IDs, like say
public class YourComboItem
public property Id as string [get/set]
public property Title as string [get/set]
end class
Then bind your ComboBox's ItemsSource to a collection of these items retrieved from the database, and set DisplayMemberPath to Title and ValueMemberPath to Id. Then you can use the ComboBox's SelectedValue to get the selected ID. As you can see, having objects instead of raw data structures can have quite some advantages.
Of course, I described getting the SelectedValue directly from the ComboBox, while a much better architecture would be MVVM, with the ViewModel containing an ObservableCollection(Of YourComboItem) and the ComboBox's ItemSource bound to it with an actual binding. Then you can also bind the SelectedItem to a property in your ViewModel, and have the item as a whole, including both Id and Title, to work with without knowing anything about your user interface. Alternatively you could have an ICollectionView generated from the collection of items and bind the ItemsSource to that, then you'd have the selected item in the ICollectionView's CurrentItem property.
I'd really recommend reading up on MVVM (Model-View-ViewModel) to make your work with WPF a whole lot easier.

After adding a new row why aren't my bound controls updating?

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.

WPF bindings not refreshing

I have a listbox on my form that looks like this:
<ListBox Name="lbResults" SelectionChanged="lbResults_SelectionChanged"/>
I am binding the following collection to it:
ObservableCollection<Hand> oHands = new ObservableCollection<Hand>();
using the following code:
lbResults.DataContext = oHands;
Binding binding = new Binding();
lbResults.SetBinding(ListBox.ItemsSourceProperty, binding);
The oHands collection gets populated via a background worker that announces via an event whenever a new Hand object is available. The ListBox refreshes perfectly when something is added. The ToString() result of the Hand object is displayed and that is what I want - so far so good. However, when the background worker finishes
void finder_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
oHands = new ObservableCollection<Hand>(oHands.OrderBy(o => o.PotSize));
lbResults.SetBinding(ListBox.ItemsSourceProperty, new Binding());
}
The items in the list are still showing up in the original order. I can confirm that the list is re-ordered but the items are still showing up in the original order. How do I refresh this binding?
you dont want to assign oHands a new collection. just Clear() the collection then add the results from the operation. don't update the binding
instead of replacing the entire observable collection, you could just clear it and add all your new items. that wouldn't affect your binding.
You could also use a CollectionViewSource as your binding, and set the order on that instead of reordering the whole collection.
Wouldn't it be a lot easier to just set the itemsource directly?
lbResults.ItemsSource = oHands;
You're really just supposed to inherit from the INotifyPropertyChanged interface, but heres another way to force an update to a binding:
BindingExpression exp = BindingOperations.GetBindingExpression(lbResults, Listbox.ItemsSourceProperty)
exp.UpdateTarget()
Edit: I also just noticed you aren't setting any binding in the XAML and appear to be doing it programmatically with an empty Binding. I haven't tried that way before, so see if changing your XAML to this might help:
<ListBox Name="lbResults" SelectionChanged="lbResults_SelectionChanged" ItemsSource="{Binding Path=oHands}"/>
Then you set lbResults.DataContext to point to the class that has the member oHands. This is what worked for me in my project (in IronPython, so forgive me if my examples didn't convert to C# perfectly).

WPF DataGrid Row add in codebehind

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.

Resources