I started writing an application and I am facing trouble with the databinding of an observablecollection, because I am not very familiar with WPF an Binding. Further the different method of binding like object binding, xaml binding are confusing me.
The idea is to retrieve data from a SQL statement and add them to a observablecollection. Afterwards textboxes/comboboxes which are located in a tab in the mainwindows should be updated with this data.
I have a SQL Class which is retrieving the data from a sqlserver and populating the observablecollection. The following code is working at the moment:
Imports System.Data.Sql
Imports System.Data.SqlClient
Imports System.Data
Imports System.Collections.ObjectModel
Imports System.Xml
Imports System.Xml.Linq
Public Class SQLQueries
Public Sub GetPersonData(ByVal HRID_TextBox_OnB As String)
Dim con As New SqlConnection(My.Settings.AppConnString.ToString) 'connectionstring is retrieved from app settings
Dim cmd As New SqlCommand(QPersonDataQuery & "and person.personnelnumber = #DBG_HRID", con)
cmd.Parameters.AddWithValue("#DBG_HRID", HRID_TextBox_OnB)
Dim PersonData As New ObservableCollection(Of String) PersonData.Clear()
Try
con.Open()
Dim reader As SqlDataReader = cmd.ExecuteReader()
If (reader.HasRows) Then
While (reader.Read())
For i = 0 To reader.FieldCount - 1
PersonData.Add(i)
Next i
End While
End If
Catch ex As Exception
MessageBox.Show("Better call Saul!!" & vbCrLf & vbCrLf + ex.Message)
Finally
If con.State <> ConnectionState.Closed Then con.Close()
End Try
con.Dispose()
End Sub
End Class
My XAML is looking like this at the moment:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FAT Client Primary Access" Height="609" Width="811" Background="White">
<Grid>
<Menu IsMainMenu="True" Height="28" VerticalAlignment="Top" Background="White">
<MenuItem Header="_File">
<MenuItem Header="_Close" Click="CloseApp_Click"/>
</MenuItem>
<MenuItem Header="_Database">
<MenuItem Header="_Check Connection" Click="CheckConnection_Click"/>
<MenuItem Header="_Change Connection String" Click="ChangeConnection_Click"/>
</MenuItem>
</Menu>
<TabControl Height="544" HorizontalAlignment="Left" Margin="0,26,0,0" Name="TabControl1" VerticalAlignment="Top" Width="789" Background="White">
<TabItem Header="Onboarding" Name="TabItem1" Background="White">
<Grid Background="White" Width="797" Height="524">
<Label Content="HRID" Height="27" HorizontalAlignment="Left" Margin="32,24,0,0" Name="Label1" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="74,24,0,0" Name="TextBox1" VerticalAlignment="Top" Width="83" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="182,23,0,0" Name="Button1" VerticalAlignment="Top" Width="75" />
<TextBox Height="26" HorizontalAlignment="Left" Margin="158,104,0,0" Name="TextBox2" VerticalAlignment="Top" Width="124" />
</Grid>
</TabItem>
<TabItem Header="other Trigger" Name="TabItem2" Background="White">
<Grid Background="White" />
</TabItem>
</TabControl>
</Grid>
</Window>
Unfortunately I have no idea how to bind the first value in my observablecollection to textbox2. I tried and read a lot but this was more confusing then helping out.
Do I need a separate Class to bind the observablecollection to the textboxes?
I would appreciate it if could give a me small hint.
Thanks in advance. And also Hello everybody.
<TextBox x:Name="textBox2" Text="{Binding [2].Name}" />
This binding path will show Name property of 3rd element of your ObservableCollection.
<TextBox x:Name="textBox2" Text="{Binding [0].Name}" />
So, this will show Name of first element of your collection.
But how Textbox will know what is the collection to use ? For that you have to set DataContext either at root level, or at your TextBox level. This root can be anywhere above your TextBox. DataContext set at this root will be visible within all children of this root only.
Your data can be complex like Country>States>City.For example, you can set DataContext to Country at root level, and some child elements can then use States, while some can use City etc.
So, set DataContext like this :
Imports System.Collections.ObjectModel
Class MainWindow
Public Property InvoiceCollection As New ObservableCollection(Of Invoice)
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Fill your InvoiceCollection.
...
...
Me.DataContext = InvoiceCollection
End Sub
End Class
Then you can show TotalPrice of first Invoice item like :
<TextBox x:Name="textBox2" Text="{Binding [0].TotalPrice}" />
You can even set DataContext of TextBox2 like :
TextBox2.DataContext = InvoiceCollection[0]
then databinding will look like :
<TextBox x:Name="textBox2" Text="{Binding TotalPrice}" />
You can even set DataContext of TextBox2 like :
TextBox2.DataContext = InvoiceCollection
then databinding will look like :
<TextBox x:Name="textBox2" Text="{Binding [0].TotalPrice}" />
Few important links :
DataBinding in 7 part series
Property path syntax
Related
I'm based on the official Microsoft sample to create a MasterDetail ListView:
MasterDetail ListView UWP sample
I have adapted it to my case, as I want that users can edit directly selected items from the ListView. But I meet a strange comportement:
when I add a new item to the ListView, the changes of the current item, done in the details container, are well saved
but when I select an existing item in the ListView, the changes of the current item, done in the details container, are not saved
Here is a screenshot of my app:
The XAML of my ListView is like this:
<!-- Master : List of Feedbacks -->
<ListView
x:Name="MasterListViewFeedbacks"
Grid.Row="1"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource MasterListViewFeedbacksItemTemplate}"
IsItemClickEnabled="True"
ItemsSource="{Binding CarForm.feedback_comments}"
SelectedItem="{Binding SelectedFeedback, Mode=TwoWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.FooterTemplate>
<DataTemplate>
<CommandBar Background="White">
<CommandBar.Content>
<StackPanel Orientation="Horizontal">
<AppBarButton Icon="Add" Label="Add Feedback"
Command="{Binding AddItemFeedbacksCommand}" />
<AppBarButton Icon="Delete" Label="Delete Feedback"
Command="{Binding RemoveItemFeedbacksCommand}" />
</StackPanel>
</CommandBar.Content>
</CommandBar>
</DataTemplate>
</ListView.FooterTemplate>
</ListView>
The XAML of the ListView's ItemTemplate is:
<DataTemplate x:Key="MasterListViewFeedbacksItemTemplate" x:DataType="models:Feedback_Comments">
<StackPanel Margin="0,11,0,13"
Orientation="Horizontal">
<TextBlock Text="{x:Bind creator }"
Style="{ThemeResource BaseTextBlockStyle}" />
<TextBlock Text=" - " />
<TextBlock Text="{x:Bind comment_date }"
Margin="12,1,0,0" />
</StackPanel>
</DataTemplate>
The XAML of the Details container is like this:
<!-- Detail : Selected Feedback -->
<ContentPresenter
x:Name="DetailFeedbackContentPresenter"
Grid.Column="1"
Grid.RowSpan="2"
BorderThickness="1,0,0,0"
Padding="24,0"
BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
Content="{x:Bind MasterListViewFeedbacks.SelectedItem, Mode=OneWay}">
<ContentPresenter.ContentTemplate>
<DataTemplate x:DataType="models:Feedback_Comments">
<StackPanel Visibility="{Binding FeedbacksCnt, Converter={StaticResource CountToVisibilityConverter}}">
<TextBox Text="{Binding creator, Mode=TwoWay}" />
<DatePicker Date="{Binding comment_date, Converter={StaticResource DateTimeToDateTimeOffsetConverter}, Mode=TwoWay}"/>
<TextBox TextWrapping="Wrap" AcceptsReturn="True" IsSpellCheckEnabled="True"
Text="{Binding comment, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ContentPresenter.ContentTemplate>
<ContentPresenter.ContentTransitions>
<!-- Empty by default. See MasterListView_ItemClick -->
<TransitionCollection />
</ContentPresenter.ContentTransitions>
</ContentPresenter>
The "CarForm" is the main object of my ViewModel. Each CarForm contains a List of "Feedback_Comments".
So in my ViewModel, I do this when I add a new comment:
private void AddItemFeedbacks()
{
FeedbacksCnt++;
CarForm.feedback_comments.Add(new Feedback_Comments()
{
sequence = FeedbacksCnt,
creator_id = user_id,
_creator = username,
comment_date = DateTime.Now
});
SelectedFeedback = CarForm.feedback_comments[CarForm.feedback_comments.Count - 1];
}
=> the changes done in the Feedback_Comment that was edited before the add are well preserved
I don't do anything when the user select an existing Feedback_Comment: this is managed by the XAML directly.
=> the changes done in the Feedback_Comment that was edited before to select anoter one are not preserved
=> Would you have any explanation?
The TwoWay binding for the Text property is updated only when the TextBox loses focus. However, when you select a different item in the list, the contents of the TextBox are no longer bound to the original item and so are not updated.
To trigger the update each time the Text contents change, so that the changes are reflected immediately, set the UpdateSourceTrigger set to PropertyChanged:
<TextBox Text="{Binding comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Triggering changes everywhere
To ensure your changes are relflected everywhere including the list, you will need to do two things.
First, your feedback_comments is of type ObservableCollection<Feedback_Comments>. This ensures that the added and removed items are added and removed from the ListView.
Second, the Feedback_Comments class must implement the INotifyPropertyChanged interface. This interface is required to let the user interface know about changes in the data-bound object properties.
Implementing this interface is fairly straightforward and is described for example on MSDN.
The quick solution looks like this:
public class Feedback_Comments : INotifyPropertyChanged
{
// your code
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged( [ CallerMemberName ]string propertyName = "" )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
Now from each of your property setters call OnPropertyChanged(); after setting the value:
private string _comment = "";
public string Comment
{
get
{
return _comment;
}
set
{
_comment = value;
OnPropertyChanged();
}
}
Note, that the [CallerMemberName] attribute tells the compiler to replace the parameter by the name of the caller - in this case the name of the property, which is exactly what you need.
Also note, that you can't use simple auto-properties in this case (because you need to call the OnPropertyChanged method.
Bonus
Finally as a small recommendation, I see you are using C++-like naming conventions, which does not fit too well into the C# world. Take a look at the recommended C# naming conventions to improve the code readability :-) .
I am using Listbox and it contains button ,and i want to handle button click event using command.but my command never calls.
is this Correct way??
<pmControls:pmListBox Grid.Row="1" Margin="3" ItemsSource="{Binding Countries}" SelectedItem="{Binding SelectedCountry}" >
<pmControls:pmListBox.ItemTemplate >
<DataTemplate >
<Button Command="{Binding GetAllStatesCommand}" CommandParameter="{Binding}" Margin="3" Width="100" Height="50" Content="{Binding Title}">
</Button>
</DataTemplate>
</pmControls:pmListBox.ItemTemplate>
</pmControls:pmListBox>
The DataContext of one list item is different from the DataContextof the surrounding control. To bind that command to the DataContext of that control you have two options:
Either you provide the control with a name and reference to that:
<pmControls:pmListBox x:Name="myCoolListBox" [...]>
<pmControls:pmListBox.ItemTemplate>
<DataTemplate>
<Button Command="{Binding DataContext.GetAllStatesCommand, ElementName=myCoolListBox}" CommandParameter="{Binding}" [...] />
</DataTemplate>
</pmControls:pmListBox.ItemTemplate>
</pmControls:pmListBox>
Or you create class holding your DataContext...
public class DataContextBinder : DependencyObject
{
public static readonly DependencyProperty ContextProperty = DependencyProperty.Register("Context", typeof(object), typeof(DataContextBinder), new PropertyMetadata(null));
public object Context
{
get { return GetValue(ContextProperty); }
set { SetValue(ContextProperty, value); }
}
}
...and create an instance of that in the resources section of your ListBox:
<pmControls:pmListBox x:Name="myCoolListBox" [...]>
<pmControls:pmListBox.Resources>
<local:DataContextBinder x:Key="dataContextBinder" Context="{Binding}" />
</pmControls:pmListBox.Resources>
<pmControls:pmListBox.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Context.GetAllStatesCommand, Source={StaticResource dataContextBinder}" CommandParameter="{Binding}" [...] />
</DataTemplate>
</pmControls:pmListBox.ItemTemplate>
</pmControls:pmListBox>
How to use trigger event in a button inside a datagrid in silverlight using relaycommand mvvm
Iam unable to get selected values in to some Dto , it means once i selected a row for delete , the selected item property showing NULL .how to solve it pls
You can use trigger event like below in datagrid:
<Button Content="Message" Height="23" HorizontalAlignment="Left" Margin="234,116,0,0" Name="btnMsg" VerticalAlignment="Top" Width="75" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<si:CallDataMethod Method="HandleShowMessage"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
You have to add necessary reference for this.
For selecteditem you have to set selected item into datagrid and other thing you have to decalre a property in viewmodel:
In Xaml:
<sdk:DataGrid Height="Auto" AutoGenerateColumns="False" ItemsSource="{Binding Emp}" SelectedItem="{Binding SelectedEMp,Mode=TwoWay}" BorderThickness="1" HorizontalAlignment="Left" Name="dataGrid1" VerticalAlignment="Top" Width="auto">
and in Viewmodel:
private EmpInfo _selectedEMp;
public EmpInfo SelectedEMp
{
get { return _selectedEMp; }
set
{
_selectedEMp = value;
on("SelectedEMp");
}
}
Thanks
I have the below xaml that I am trying to bind to my class. I am having trouble getting the values to show up. Can anyone point me in the direction of what I am missing. Thank you in advance.
Dim frm As New EditPart
frm.DataContext = New SelectedPart(_CPPartPicker.Selected_Part, "ABC")
frm.Show()
Class SelectedPart
Property Part_Key As Integer
Property Part_Id As String
Property Part_Rev As String
Property Whse As String
Property Part_Description As String
Sub New(Part As SNC.SL.Common.CP_Item.CP_Item_Lookup_Version_1Item_Lookup_Response, Whse As String)
Part_Key = Part.ITEM_KEY
Part_Id = Part.ITEM_ID
Part_Rev = Part.ITEM_RVSN_ID
Whse = Whse
Part_Description = Part.ITEM_DESC
End Sub
End Class
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<sdk:Label Content="{Binding Path=Part_Id, StringFormat='Part ID: \{0}'}" />
<sdk:Label Content="{Binding Path=Part_Rev, StringFormat='Part Rev: \{0}'}" />
<sdk:Label Content="{Binding Path=Part_Description, StringFormat='Description: \{0}'}"/>
<Button x:Name="CancelButton" Content="Cancel" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
<Button x:Name="OKButton" Content="OK" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
</Grid>
In the ouput window I get the below error message:
Cannot get 'Part_Id' value (type 'System.String') from 'SNC.CommonStock.SelectedPart' (type 'SNC.CommonStock.SelectedPart'). BindingExpression: Path='Part_Id' DataItem='SNC.CommonStock.SelectedPart' (HashCode=53866394); target element is 'System.Windows.Controls.Label' (Name=''); target property is 'Content' (type 'System.Object').. System.MethodAccessException: Attempt by method 'System.Windows.CLRPropertyListener.get_Value()' to access method 'SNC.CommonStock.SelectedPart.get_Part_Id()' failed.
The labels are all on top of one another at present, if there is no description then potentially you'll see no content. Place the labels in a StackPanel.
I had to make the class I was binding to public
I have tried to set up combo boxes in the gridview but all the combo boxes have the same value in them instead of the value from the database. I am using entity framework and WPF. There is a parent child relationship between two tables but the source for the combo box is a separate table with names and IDs for tags. I have been looking all day. Hopefully this won't be too easy to solve.
The "Tag" Column displays the combo box. The Column "Tag ID" displays the value from the database. When I display the data the TagID Changes in diffrent rows but the Tag column is the same (the first choice) in all the rows. When I change one combo box they all change. I can't see where they are hooked together. Any assistance you can provide would be appreciated. (Buler?)
Here is the XAML
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="372" Width="675" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:my="clr-namespace:TagFinanceWPF">
<Window.Resources>
<CollectionViewSource x:Key="TransactionsViewSource" d:DesignSource="{d:DesignInstance my:Transaction, CreateList=True}" />
<CollectionViewSource x:Key="TransactionsTransactionTagsViewSource" Source="{Binding Path=TransactionTags, Source={StaticResource TransactionsViewSource}}" />
<CollectionViewSource x:Key="TagLookup" />
</Window.Resources>
<Grid DataContext="{StaticResource TransactionsViewSource}">
<ListView ItemsSource="{Binding Source={StaticResource TransactionsTransactionTagsViewSource}}" Margin="12" Name="TransactionTagsListView" SelectionMode="Single">
<ListView.ItemContainerStyle>
<Style>
<Setter Property="Control.HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Control.VerticalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn x:Name="TransactionIDColumn1" Header="Transaction ID" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Path=TransactionID}" Margin="6,-1,-6,-1" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn x:Name="TagIDColumn" Header="Tag" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox Margin="-6,-1"
ItemsSource="{Binding Source={StaticResource TagLookup}}"
DisplayMemberPath="TagName"
SelectedValuePath="TagID"
SelectedValue="{Binding TagID}"
IsReadOnly="True">
</ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn x:Name="TagIDColumn2" Header="Tag ID" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Path=TagID}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
The VB code is:
Class MainWindow
Dim BentleyvideoEntities As TagFinanceWPF.bentleyvideoEntities = New TagFinanceWPF.bentleyvideoEntities()
Private Function GetTransactionsQuery(ByVal BentleyvideoEntities As TagFinanceWPF.bentleyvideoEntities) As System.Data.Objects.ObjectQuery(Of TagFinanceWPF.Transaction)
Dim TransactionsQuery As System.Data.Objects.ObjectQuery(Of TagFinanceWPF.Transaction) = BentleyvideoEntities.Transactions
'Update the query to include TransactionTags data in Transactions. You can modify this code as needed.
TransactionsQuery = TransactionsQuery.Include("TransactionTags")
'Returns an ObjectQuery.
Return TransactionsQuery
End Function
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
'Load data into Transactions. You can modify this code as needed.
Dim TransactionsViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("TransactionsViewSource"), System.Windows.Data.CollectionViewSource)
Dim TransactionsQuery As System.Data.Objects.ObjectQuery(Of TagFinanceWPF.Transaction) = Me.GetTransactionsQuery(BentleyvideoEntities)
TransactionsViewSource.Source = TransactionsQuery.Execute(System.Data.Objects.MergeOption.AppendOnly)
'Load data into Tags. You can modify this code as needed.
Dim customerList = From c In BentleyvideoEntities.Tags _
Order By c.TagName
Dim custSource = CType(Me.FindResource("TagLookup"), CollectionViewSource)
custSource.Source = customerList.ToList()
End Sub
End Class
I found this while researching your issue and it sounds like the exact same issue you are experiencing.
Snippit from link:
I'm not sure if this will help, but I
was reading in Chris Sells 'Windows
Forms Binding in C#, footnote, page
482', that the data source is bound to
each combobox and is managed by a
common Binding manager which in turn
is part of a Binding Context. The
Binding amager keeps all comboboxes
synchronized to the same row in the
database. However, if each combobox
has a different Binding context, hence
a diferent Binding Manager, then the
combo boxes can show different rows
from the same data source.
Based on this second article (and the suggested solution) you would need to use the row databinding event to set up the combobox's binding so that a new instace of the binding manager is created for each row bind.