WPF VB.NET ComboBox DataBinding - wpf

I'm new to LINQ to SQL and WPF and i'm using several online guides and YouTube Videos to pick this up (along with this application).
The problem:
On loading the window (and on other key data changes that refresh the Windows DataContext), I need to update what selected item the ComboBox is showing to the item with a matching ID from the Window DataContext binding (c_Instance.FK_RiskIndicator_ID)
I populate the ComboBox from a view which returns the display field and the ID (All ID's are GUIDs).
When the selection is changed, i call a Stored Procedure to update the DB with the ID of the selected item.
Private db As New AppDatabase_DBDataContext
Private CRCTypeView As BindingListCollectionView
Private InstanceDetailTypeView As BindingListCollectionView
Property guInstance_ID As Guid
Private Sub Refresh_PCA_Instance()
Dim c_Instance As IEnumerable(Of t_Instance)
c_Instance = (From Inst In db.t_Instances Where Inst.ID = guInstance_ID Select Inst)
If c_Instance.Count = 1 Then
Me.DataContext = c_Instance
Me.InstanceDetailTypeView = CType(CollectionViewSource.GetDefaultView(Me.DataContext), BindingListCollectionView)
ElseIf c_Instance.Count > 1 Then
Me.DataContext = c_Instance.First
Me.InstanceDetailTypeView = CType(CollectionViewSource.GetDefaultView(Me.DataContext), BindingListCollectionView)
End If
End Sub
Private Sub LoadCRCType()
Dim CRCTypeList As IEnumerable(Of vw_RiskIndicator)
CRCTypeList = (From RiskIndicator In db.vw_RiskIndicators
Order By RiskIndicator.IndexNum
Select RiskIndicator)
PCA_CRC_ComboBox.DataContext = CRCTypeList
Me.CRCTypeView = CType(CollectionViewSource.GetDefaultView(PCA_CRC_ComboBox.DataContext), BindingListCollectionView)
End Sub
Private Sub PCA_CRC_ComboBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles PCA_CRC_ComboBox.SelectionChanged
If PCA_CRC_ComboBox.SelectedItem IsNot Nothing Then
db.sp_Instance_RiskIndicator_UPSERT(guInstance_ID, PCA_CRC_ComboBox.SelectedValue)
Refresh_PCA_Instance() 'Refreshes the Window DataContext
End If
End Sub
XAML for the ComboBox:
<ComboBox x:Name="PCA_CRC_ComboBox" Grid.Row="1" Grid.Column="2"
ItemsSource="{Binding}"
SelectedValuePath="ID"
DisplayMemberPath="RiskIndicator"
PreviewKeyDown="RiskIndicator_ComboBox_PreviewKeyDown"/>
I had assumed i could do this with binding the SelectedItem, but i can't figure it out. I had assumed it would be something like:
<ComboBox x:Name="PCA_CRC_ComboBox" Grid.Row="1" Grid.Column="2"
SelectedItem="{Binding Path=FK_RiskIndicator_ID}" <!--THIS LINE HERE-->
ItemsSource="{Binding}"
SelectedValuePath="ID"
DisplayMemberPath="RiskIndicator"
PreviewKeyDown="RiskIndicator_ComboBox_PreviewKeyDown"/>
Any help with this would be much appreciated.
EDIT: Grouped the code together. I assume this is what was meant. Personally i think this layout if less explanatory.

Related

WPF binding with observerableCollection doesnt work

I use for my WPF project a binding of a table (db) to a listview. But if i start my project, the listview is empty. Iam using linq to get the data of my entity-framework and address has definitly the right string in it.
Is my binding is wrong and how to fix it?
xaml
<ListView ItemsSource="{Binding Items}" x:Name="lstvw_Overview" >
<ListView.View>
<GridView>
<GridViewColumn Header="Adresse"
DisplayMemberBinding="{Binding structureAddress}"/>
</GridView>
</ListView.View>
</ListView>
code
Iam pretty sure that my table is filled
Public Sub New()
Initialize()
End Sub
Dim address As String
Public Structure Uebersicht
Dim structureAddress As String
Shared _items As ObservableCollection(Of Uebersicht) = New ObservableCollection(Of Uebersicht)
Public Property Items As ObservableCollection(Of Uebersicht)
Get
Return _items
End Get
Set(value As ObservableCollection(Of Uebersicht))
_items = value
End Set
End Property
End Structure
Sub Initialize()
InitializeComponent()
DataContext = Me
fillListView()
End Sub
Sub fillListView()
Using container As New infrastrukturDB_TESTEntities1
Dim mailAddressList = From tbl_unzustellbarAdressen In container.tbl_unzustellbarAdressen
For Each mail In mailAddressList
address = mail.unzustellbarMail.ToString()
Try
Uebersicht._items.Add(New Uebersicht With {.structureAddress = address})
Catch ex As Exception
MessageBox.Show("error")
End Try
Next
End Using
End Sub
For WPF bindings, always provide a Property definition, fields are not supported by the Binding class. So you need to turn structureAddress into a property in order to make it work.
Since you set your Window (?) class instance as its own DataContext and try to bind ItemsSource="{Binding Items}", your window class needs to contain a Property named Items with some collection type (eg ObservableCollection). So move your collection from within Structure Uebersicht to the outer Window class and don't use Shared on the backing field.
Note you don't really need the property setter on Items since you initialize _items once and then you only ever modify the contained items but not the collection reference itself.

Scroll WPF DataGrid to show selected item on top

I have a DataGrid with many items and I need to programmatically scroll to the SelectedItem. I have searched on StackOverflow and Google, and it seems the solution is ScrollIntoView, as follows:
grid.ScrollIntoView(grid.SelectedItem)
which scrolls the DataGrid up or down until the selected item is in focus. However, depending on the current scroll position relative to the selected item, the selected item may end up being the last visible item in the DataGrid's ScrollViewer. I want that the selected item will be the first visible item in the ScrollViewer (assuming there are enough rows in the DataGrid to allow this). So I tried this:
'FindVisualChild is a custom extension method that searches in the visual tree and returns
'the first element of the specified type
Dim sv = grid.FindVisualChild(Of ScrollViewer)
If sv IsNot Nothing Then sv.ScrollToEnd()
grid.ScrollIntoView(grid.SelectedItem)
First I scroll to the end of the DataGrid and only then do I scroll to the SelectedItem, at which point the SelectedItem is shown at the top of the DataGrid.
My problem is that scrolling to the end of the DataGrid works well, but subsequently scrolling to the selected item doesn't always work.
How can I resolve this issue, or is there any other alternative strategy for scrolling to a specific record in the top position?
You were on the right track, just try to work with collection view instead of working directly on the datagrid for this kind of needs.
Here is a working example where the desired item is always displayed as first selected item if possible, otherwise the scrollviewer is scrolled to the end and the target item is selected at its position.
The key points are :
Use CollectionView on the business side and enable current item synch on the XAML control (IsSynchronizedWithCurrentItem=true)
Defer the "real" target scroll in order to allow the "Select Last item" to be visualy executed (By using a Dispatcher.BeginInvoke with a low priority)
Here is the business logic (This is automatic convertion from C# to VB)
Public Class Foo
Public Property FooNumber As Integer
Get
End Get
Set
End Set
End Property
End Class
Public Class MainWindow
Inherits Window
Implements INotifyPropertyChanged
Private _myCollectionView As ICollectionView
Public Sub New()
MyBase.New
DataContext = Me
InitializeComponent
MyCollection = New ObservableCollection(Of Foo)
MyCollectionView = CollectionViewSource.GetDefaultView(MyCollection)
Dim i As Integer = 0
Do While (i < 50)
MyCollection.Add(New Foo)
i = (i + 1)
Loop
End Sub
Public Property MyCollectionView As ICollectionView
Get
Return Me._myCollectionView
End Get
Set
Me._myCollectionView = value
Me.OnPropertyChanged("MyCollectionView")
End Set
End Property
Private Property MyCollection As ObservableCollection(Of Foo)
Get
End Get
Set
End Set
End Property
Private Sub ButtonBase_OnClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim targetNum As Integer = Convert.ToInt32(targetScroll.Text)
Dim targetObj As Foo = Me.MyCollection.FirstOrDefault(() => { }, (r.FooNumber = targetNum))
'THIS IS WHERE THE MAGIC HAPPENS
If (Not (targetObj) Is Nothing) Then
'Move to the collection view to the last item
Me.MyCollectionView.MoveCurrentToLast
'Bring this last item into the view
Dim current = Me.MyCollectionView.CurrentItem
itemsContainer.ScrollIntoView(current)
'This is the trick : Invoking the real target item select with a low priority allows previous visual change (scroll to the last item) to be executed
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, New Action(() => { }, Me.ScrollToTarget(targetObj)))
End If
End Sub
Private Sub ScrollToTarget(ByVal targetObj As Foo)
Me.MyCollectionView.MoveCurrentTo(targetObj)
itemsContainer.ScrollIntoView(targetObj)
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler
Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
If (Not (PropertyChanged) Is Nothing) Then
PropertyChanged?.Invoke(Me, New PropertyChangedEventArgs(propertyName))
End If
End Sub
End Class
And this is the xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid x:Name="itemsContainer" ItemsSource="{Binding MyCollectionView}" IsSynchronizedWithCurrentItem="True" Margin="2" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding FooNumber}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="1">
<TextBox x:Name="targetScroll" Text="2" Margin="2"></TextBox>
<Button Content="Scroll To item" Click="ButtonBase_OnClick" Margin="2"></Button>
</StackPanel>
</Grid>
I Solved this question with following code:
public partial class MainWindow:Window
{
private ObservableCollection<Product> products=new ObservableCollection<Product> ();
public MainWindow()
{
InitializeComponent ();
for (int i = 0;i < 50;i++)
{
Product p=new Product { Name="Product "+i.ToString () };
products.Add (p);
}
lstProduct.ItemsSource=products;
}
private void lstProduct_SelectionChanged(object sender,SelectionChangedEventArgs e)
{
products.Move (lstProduct.SelectedIndex,0);
lstProduct.ScrollIntoView (lstProduct.SelectedItem);
}
}
public class Product
{
public string Name { get; set; }
}
<Grid>
<ListBox Name="lstProduct" Margin="20" DisplayMemberPath="Name" SelectionChanged="lstProduct_SelectionChanged" />
</Grid>
The accepted answer to this other question shows a different approach to get the first/last visible row of such a grid.
You could find out the index of your row and directly scroll there or scroll down row by row until the first visible row matches.

Open user control by button in another user control

So, my set up is like this:
My MainWindow has a TabControl with 5 tabs. To keep the code in my file clean I decided the 5 tabs their content into 5 UserControls.
In let say tab 1 has UserControl Customers (View: CustomersView, ViewModel: CustomersViewModel) with a ComboBox. The ComboBox is ItemSource is bound to a ObservableCollection(Of String) which is filled by DbServiceKlant.GetKlantenlijst.
Also in tab 1 I have a button to add a new Customer to the database. This opens another UserControl which overlays the entire MainWindow.
This 2nd UserControl (View: AddCustomerView, ViewModel: AddCustomerViewModel) has some form to fill in, has a Sub to ass the data to the database.
I first did this all in the MainWindow but the code has taken on sushi proportions (both in .xaml and .xaml.vb) that it isn't maintainable anymore. Having everything in 1 file, it's easy to pass variables and add items to a ObservableCollection.
With everything split up in UserControls, it has been a question to me how I'm going to achieve this. Basically:
UserControl Customers has a ObservableCollection filled with Customer and a button to open UserControl AddCustomer.
In UserControl AddCustomer I have a button which can add the filled in form to the database.
Now the question is:
How do I open UserControl AddCustomer from Customers?
How do I update my ObservableCollection in Customers if I close AddCustomer?
When everything was in 1 file I just manipulated Visibility of AddCustomer and could update the ObservableCollection.
This is a snippet from Customers.
<ComboBox Background="White" ItemsSource="{Binding Klantenlijst}" SelectedItem="{Binding SelectedKlant}" Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="4" />
<Button Grid.Column="5" Command="{Binding OpenAddKlant}" ToolTip="Nieuwe klant">
<Image Source="/Images/Add.png" />
</Button>
The ObservableCollection.
Private _oKlantenlijst As ObservableCollection(Of String) = DbServiceKlant.GetKlantenlijst
Public Property Klantenlijst As ObservableCollection(Of String)
Get
Return _oKlantenlijst
End Get
Set(ByVal value As ObservableCollection(Of String))
_oKlantenlijst = value
OnPropertyChanged()
End Set
End Property
The ICommand:
Private _oOpenAddKlant As New RelayCommand(AddressOf OpenAddKlantSub)
Public ReadOnly Property OpenAddKlant As RelayCommand
Get
Return _oOpenAddKlant
End Get
End Property
And the actual Sub
Public Sub OpenAddKlantSub()
' I think here should be the code to open the AddCustomer UserControl
End Sub
In my 1 file application (which I'm converting to UserControls), the AddCustomer popup had this Sub
Private Sub PopUpAddKlantAdd(sender As Object, e As RoutedEventArgs)
Try
If PupAddKlaNaa.Equals("") OrElse PupAddKlaStr.Equals("") OrElse PupAddKlaGem.Equals("") Then
MessageBox.Show("U moet minstens de naam, straat en gemeente invullen om een nieuwe klant aan te kunnen maken.", "Geen gegevens", MessageBoxButton.OK, MessageBoxImage.Information)
Else
DbServiceKlant.Create(PupAddKlaNaa, PupAddKlaStr, PupAddKlaNr_, PupAddKlaBus, PupAddKlaPos, PupAddKlaGem, PupAddKlaLan, PupAddKlaTel, PupAddKlaBTW)
_oKlant = DbServiceKlant.GetDetails(PupAddKlaNaa)
PopUpAddKlantClose()
PopUpAddKlantClear()
VoGetNaamKlanten()
VoFillDetailsKlant(_oKlant)
VoKlaSel = _oKlant.Naam
End If
Catch ex As Exception
ExceptionLogger.Log(ex, 1)
End Try
End Sub

Get content Focus on WPF DataGrid

so im really not good at this xaml thing and i tried to look everywhere but couldn't find anything usefull for me, hopefully someone will be able to help me here.
So i have a datagrid with TemplateColumns where i have some controls in it such TextBox's and ComboBox's. What im trying to accomplish here is when i tab from one control i would like to focus on the next control in the same row but what is happening now is the column gets focus and only after that when i press tab again the control will be focus, in less words i have to tab twice to jump from one control to another. My datagrid looks like this:
<DataGridTemplateColumn Header="Omschrijving" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBox TabIndex="0" Name="txtOms" Text="{Binding txtOmschrijving}" Width="140" Height="24" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Since no one could give a hint or help i kinda digged down on the internetzz and found a solution, hopefully will help someone else in need:
Private Sub dgKasStaatRegels_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles dgKasStaatRegels.Loaded
Try
Dim CellFocusedChangedHandler As New RoutedEventHandler(AddressOf FocusChangedHandler)
[AddHandler](DataGridCell.GotKeyboardFocusEvent, CellFocusedChangedHandler)
Catch ex As Exception
WriteErrorLog("ucKasStaat", "dgKasStaatRegels_Loaded", ex)
End Try
End Sub
Private Sub FocusChangedHandler(sender As Object, args As RoutedEventArgs)
If args.RoutedEvent.RoutingStrategy = RoutingStrategy.Bubble Then
Dim DataGridCellObj As FrameworkElement = TryCast(args.OriginalSource, FrameworkElement)
If Keyboard.IsKeyDown(Key.Tab) Then
If DataGridCellObj IsNot Nothing Then
Dim txtb As TextBox = TryCast(DataGridCellObj, TextBox)
If txtb IsNot Nothing Then txtb.Focus()
Dim cb As ComboBox = TryCast(DataGridCellObj, ComboBox)
If cb IsNot Nothing Then
cb.Focus()
cb.IsDropDownOpen = True
End If
End If
End If
End If
End Sub
Public Shared Function FindParent(Of T As DependencyObject)(dependencyObject As DependencyObject) As T
Dim parent = VisualTreeHelper.GetParent(dependencyObject)
If parent Is Nothing Then
Return Nothing
End If
Dim parentT = TryCast(parent, T)
Return If(parentT, FindParent(Of T)(parent))
End Function
Quick explanation: on the datagrid load add a CellFocusedChangedHandler handler and in that sub just track if the object inside that row is a Textbox(in my case) and set its focus!
It worked for me!

How To Link ComboBoxItem to File

This question is a follow-up to one I asked and got answered here: How to display XPS document using a selected combobox item
I've created a WPF app using VB 2010. I set the comboboxitems via the XAML. However, I can't seem to figure out how to set the value of each item to a file path.
The objective is for a user to be able to select an item from a drop-down list, then that selection opens an XPS file in the DocumentViewer. The code below was provided to me by COMPETENT_TECH (thanks) to read and display the value of the selected comboboxitem in the DocumentViewer.
The path to the files I want opened is C:\folder\file.xps
Private Sub Button4_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button4.Click
Try
Dim sFileName As String
sFileName = DirectCast(ComboBox1.SelectedValue, String)
If Not String.IsNullOrEmpty(sFileName) Then
Dim theDocument As New System.Windows.Xps.Packaging.XpsDocument(sFileName, System.IO.FileAccess.Read)
DocumentViewer1.Document = theDocument.GetFixedDocumentSequence()
End If
Catch ex As Exception
MessageBox.Show("ERROR: " & ex.Message)
End Try
End Sub
Thanks in advance for your assistance.
Update
Here's the XAML I'm using:
<ComboBox Width="Auto" IsReadOnly="True" IsEditable="True" Name="ComboBox1" Height="Auto" Margin="0" Padding="1" Grid.Column="2">
<ComboBoxItem>123456</ComboBoxItem>
<ComboBoxItem>123457</ComboBoxItem>
<ComboBoxItem>123458</ComboBoxItem>
</ComboBox>
Depending on precisely how the xaml to load the combobox is specified, you will probably want to change this line:
Dim theDocument As New System.Windows.Xps.Packaging.XpsDocument(sFileName, System.IO.FileAccess.Read)
to:
Dim theDocument As New System.Windows.Xps.Packaging.XpsDocument(System.IO.Path.Combine("C:\folder", sFileName & ".xps"), System.IO.FileAccess.Read)
All that we do in the new code is combine the directory where the files are stored with the file name retrieved from the combobox.
Update
The correct way to retrieve the value from the combobox is:
If ComboBox1.SelectedValue IsNot Nothing Then
sFileName = DirectCast(ComboBox1.SelectedValue, ComboBoxItem).Content.ToString()
End If
I highly recommend you use Data Binding with that ComboBox.
create a class something like following:
Class XPSDocumentInfo
{
Public Property Title As String
Public Property FileName As String
}
Create an ObservableCollection(Of XPSDocumentInfo) and bind it to ComboBox's ItemsSource.
Use DisplayMemberPath="Title" attribute on ComboBox so that it will use Title property to show text in dropdown but since you bound collection of type XPSDocumentInfo, SelectedItem property of that ComboBox with return an object of type XPSDocumentInfo.
For example,
sFileName = DirectCast(ComboBox1.SelectedValue, String)
will be changed to
sFileName = DirectCast(ComboBox1.SelectedItem, XPSDocumentInfo).FileName

Resources