Can DbDataAdapter create DataColumn objects incompatible with WPF binding? - wpf

I have a situation in which I query data from a database and fill a DataTable with it. Then Binding the DataTable to an ItemsControl is failing to update properly. The root of the problem seems somehow related to having pulled the data from a Firebird database, but I can't work out how that could possibly matter. Is it possible for an implementation of DbDataAdapter to create DataColumn objects that are incompatible with WPF binding?
Here's more detail. The ItemsControl is simple:
<ItemsControl ItemsSource="{Binding COEStudents}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition/><ColumnDefinition/><ColumnDefinition/></Grid.ColumnDefinitions>
<TextBox Text="{Binding [Foo]}" Grid.Column="0"/>
<TextBox Text="{Binding [Foo]}" Grid.Column="1"/>
<TextBox Text="{Binding [LastName]}" Grid.Column="2"/>
<TextBox Text="{Binding [LastName]}" Grid.Column="3"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My Window.DataContext is set to an object with a property like this:
public DataView COEStudents { get { return coeStudTable.DefaultView; } }
The coeStudTable is populated like this:
FirebirdSql.Data.FirebirdClient.FbConnection conn=new FirebirdSql.Data.FirebirdClient.FbConnection(
#"dialect=3;charset=NONE;connection lifetime=0;connection timeout=15;pooling=True;packet size=8192;initial catalog=localhost:c:\\Users\\kdonn\\DBs\\NE\\MIS2000.fdb;server type=Default;user id=fubar;password=blah");
FirebirdSql.Data.FirebirdClient.FbDataAdapter adapter=
new FirebirdSql.Data.FirebirdClient.FbDataAdapter( "select first 3 LastName from Student", conn);
adapter.Fill(coeStudTable);
coeStudTable.Columns.Add("Foo", typeof(string));
foreach (DataRow r in coeStudTable.Rows)
r["Foo"]=r["LastName"];
The ItemsControl shows the three rows of data, four copies of each. When I change the value in Column 0 and tab away, the value in Column 1 shows the changed value as expected. When I change the value in Column 2 and tab away, the value in Column 3 does not change.
If I switch from Firebird to SQL Server for my data as follows, everything works fine:
System.Data.SqlClient.SqlDataAdapter adapter=
new System.Data.SqlClient.SqlDataAdapter( "select top 3 LastName from HRRM",
#"Data Source=localhost\SQLEXPRESS;Initial Catalog=Viewpoint2;Integrated Security=True");
This seems to suggest that somehow the FbDataAdapter is creating DataColumn objects that don't play nice with WPF data binding, but how could that be?

Related

WPF Entity Framework Combobox (Data Binding to Foreign Key of Related Table) Display as TextBlock (SelectedValuePath Equivalent)

I'm creating a WPF data access layer that probably doesn't require full MVVM at this stage (but I might implement it).
I've successfully created a ComboBox that data binds to a foreign key value of a related table using a CollectionViewSource as the data source (See my XAML below, the combo box works fine but the TextBlock doesn't).
I only want to display the ComboBox as the cell editing template and use a TextBlock for displaying the data when it is not being edited. I can get the TextBlock to almost work (it displays data from the table related in the Foreign Key) but I can't find the equivalent property for "SelectedValuePath" so the TextBlock always displays the first value from the related table, rather than the value that corresponds to the ID in the Foreign Key field.
Is there a way (there must be) to get an equivalent behaviour from the TextBlock as I have in the ComboBox? Is there an equivalent property for SelectedValuePath?
The answer to this question would be hugely useful as there are some other fields I want to display in my data grid without providing the user any ability to edit but I still want to display a field from a related table rather than the Foreign Key ID.
Thanks so much in advance for your help and have a great day!
<CollectionViewSource x:Key="QGradeLookup"/>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Source={StaticResource QGradeLookup}, Path=QGrade}"
>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
IsEditable="False"
ItemsSource="{Binding Source={StaticResource QGradeLookup}}"
DisplayMemberPath="QGrade"
SelectedValuePath="ID"
SelectedValue="{Binding Path=OfficeQualityGradeID}"
IsSynchronizedWithCurrentItem="False"
>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
As requested here's the code for the item's source (thanks NIT):
Dim QGradeLookup As CollectionViewSource
Dim QGradeList = From q In OMRInterfaceEntities.OfficeQualityGrades
Dim QGsource = CType(Me.FindResource("QGradeLookup"), CollectionViewSource)
QGsource.Source = QGradeList.ToList()
I used Beth Massi's post as a template for the above - Beth Massi's Template
And here's the codebehind
Public Class WinPropertyDataEntry
Dim QGradeLookup As CollectionViewSource
Private Function GetOMRMarketsQuery(OMRInterfaceEntities As OMRInterfaceCustomCode.OMRInterfaceEntities) As System.Data.Objects.ObjectQuery(Of OMR.OMRInterfaceCustomCode.OMRMarket)
Dim OMRMarketsQuery As System.Data.Objects.ObjectQuery(Of OMR.OMRInterfaceCustomCode.OMRMarket) = OMRInterfaceEntities.OMRMarkets
'To explicitly load data, you may need to add Include methods like below:
'OMRMarketsQuery = OMRMarketsQuery.Include("OMRMarkets.OMRMarketType").
'For more information, please see http://go.microsoft.com/fwlink/?LinkId=157380
'Update the query to include Properties data in OMRMarkets. You can modify this code as needed.
OMRMarketsQuery = OMRMarketsQuery.Include("Properties")
'Update the query to include OMRBuildingSurveys data in OMRMarkets. You can modify this code as needed.
OMRMarketsQuery = OMRMarketsQuery.Include("Properties.OMRBuildingSurveys").Where("it.ID = 12")
'Returns an ObjectQuery.
Return OMRMarketsQuery
End Function
Private Sub Window_Loaded_1(sender As Object, e As RoutedEventArgs) Handles MyBase.Loaded
Dim OMRInterfaceEntities As OMR.OMRInterfaceCustomCode.OMRInterfaceEntities = New OMR.OMRInterfaceCustomCode.OMRInterfaceEntities()
'Load data into OMRMarkets. You can modify this code as needed.
Dim OMRMarketsViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("OMRMarketsViewSource"), System.Windows.Data.CollectionViewSource)
Dim OMRMarketsQuery As System.Data.Objects.ObjectQuery(Of OMR.OMRInterfaceCustomCode.OMRMarket) = Me.GetOMRMarketsQuery(OMRInterfaceEntities)
OMRMarketsViewSource.Source = OMRMarketsQuery.Execute(System.Data.Objects.MergeOption.AppendOnly)
Dim QGradeList = From q In OMRInterfaceEntities.OfficeQualityGrades
Dim QGsource = CType(Me.FindResource("QGradeLookup"), CollectionViewSource)
QGsource.Source = QGradeList.ToList()
End Sub
The requirement you put here can be handled in the following manner. First of all binding the TextBlock here to Collection source and giving Path is not going to give you anything.
Each row in your DataGrid represents object of type OMRBuildingSurvey which has property OfficeQualityGradeID which I assume is of type string or int. Now what you want here is whenever the ID for OfficeQualityGrade is changed, the non-editable template i.e TextBlock should display the Grade name for the selected OfficeQualityGrade.
Problem here is you are not capturing the selected OfficeGrade here. So what you need to do is to first bind SelectedItem of the Combobox to the property of type "OfficeQualityGrade" (lets say SelectedOfficeGrade). The property should raise the property change notifications. And then you can bind your textblock to this property as Text = {Binding SelectedOfficeGrade.Grade}. you will have to define this property in your entity and implement INotifyPropertychanged.
Hope this will help.
Thanks
Okay, thanks so much Nit for your help. Your solution would absolutely work in a different scenario but each building survey has it's own unique Quality Grade. The answer is that I was referencing the object incorrectly. I was using the name of the column (OfficeQualityGradeID) Rather than the name of the object OfficeQualityGrade.
The following code provides a text block for the display of Quality Grades and a Combo Box for editing Quality Grades:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding OfficeQualityGrade.QGrade}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
x:Name="QGradeSelector"
IsEditable="False"
ItemsSource="{Binding Source={StaticResource QGradeLookup}}"
DisplayMemberPath="QGrade"
SelectedValuePath="ID"
SelectedValue="{Binding Path=OfficeQualityGradeID}"
IsSynchronizedWithCurrentItem="False"
>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>

dispay data matrix with different number of columns in each row

I need to display ,in a WPF app, a 'data matrix' that has no fixed tabular structure i.e.
data row 1: aaa bb ccc
data row 2: 222 $$ ddddd eeee
...
all data is in a list of arrays.
i need to show this data and enable a user to scroll through it.
the strange data structure comes from a textual flat file, and i cannot change this.
i have read a lot, and experimented with a list-view and a grid-view, but so far i am only able to get:
1. dymanic column creation, but this is no good since i have a different schema for each row
2. rendering each row of data as (delimited/formatted) text. but this is no good since all row data is endign up in a single cell (obviously).
3. (havn't done this yet, hoping to avoid) have lots of data templates with triggers, each with a diffrent column count - say from 5 to 40 - to acomodate vast majority of row types.
my question is: how do i bind this data to a list-like / grid-like scrollable view ?
Thanks in advance
If you don't need the ability to select an item, you can use nested ItemsControls
Simply bind the first ItemsControl to your collection of arrays, and set the ItemTemplate to another ItemsControl which binds to the array of values.
Something like this:
<ScrollViewer Height="300" Width="400">
<ItemsControl ItemsSource="{Binding ListOfArrays}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }" Width="50" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
One pass to find the maximum number of columns.
Build a simple Row class that has a property cols string[maxColCount].
Build the maxColCount columns for a GridView in code bind and bind the source to cols[x].
The compiler needs a property but you can bind the column to an collection name index
Syntax for building and binding a GridViewColumn
gvc = new GridViewColumn();
gvch = new GridViewColumnHeader();
gvch.Content = fd.FieldDef.DispName;
gvch.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
if (fd.FieldDef.Sort)
{
gvch.Click += new RoutedEventHandler(SortClick);
gvch.Tag = fd.FieldDef.Name;
}
// if (fd.ID == 0 || fd.ID == 1) gvc.Width = 60; sID, sParID
if (!fd.AppliedDispGrid) gvc.Width = 0;
gvc.Header = gvch;
gvBinding = new Binding();
gvBinding.Mode = BindingMode.OneWay;
gvBinding.Path = new PropertyPath("DocFields[" + sDocBaseResultDocsFieldsIndex.ToString() + "].DispValueShort");
template = new DataTemplate();
textblock = new FrameworkElementFactory(typeof(TextBlock));
textblock.SetValue(TextBlock.TextProperty, gvBinding);
textblock.SetValue(TextBlock.TextTrimmingProperty, TextTrimming.WordEllipsis);
// <Setter Property="TextTrimming" Value="WordEllipsis" />
template.VisualTree = new FrameworkElementFactory(typeof(Grid));
template.VisualTree.AppendChild(textblock);
gvc.CellTemplate = template;
gvSearchResults.Columns.Add(gvc);

An ItemsControl is inconsistent with its items source - happening in nested ListView

This given page shows all cities for a particular state. I am displaying a ListView within a ListView. The outer ListView shows a listing of cities. The inner ListView shows all notes attached to each particular city.
Everything loads up properly. I can add a note, which gets added to the database. However, after adding a note, clicking the scroll bar in the NotesListView causes the exception:
An ItemsControl is inconsistent with its items source
I understand the problem...the listing of notes attached to the ListView becomes out of sync, after I add a note to the city.Notes property in the viewmodel... But how do I force this ListView to refresh?
Here is my XAML (edited for brevity):
<ListView x:Name="CityListView" ItemsSource="{Binding CityDetails, Mode=OneWay}">
<!--City Name and other city details. go here-->
<ListView x:Name="NotesListView" ItemsSource="{Binding Notes}">
<TextBlock Text="{Binding Path=Note}" />
</ListView>
<StackPanel>
<TextBlock Text="Add Note:" />
<TextBox Text="{Binding Path=DataContext.Note, RelativeSource={RelativeSource AncestorType=UserControl}}" />
<Button Content="ADD NOTE" Command="{Binding Path=DataContext.AddNoteCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" CommandParameter="{Binding}" />
</StackPanel>
</ListView>
Here is the AddNote() method in the ViewModel that is hooked into the AddNoteCommand:
Protected _stateService As IStateService
Protected _state As State
Protected Sub OnAddNote(city As City)
Dim note As Note = Nothing
If Not String.IsNullOrWhiteSpace(Me.Note) Then
note = New Note() With {
.Note = Me.Note,
.NoteDate = DateTime.Now,
}
city.Notes.Add(note)
_stateService.SaveExistingState(_state)
' this saves the note, since _state contains:
' Property Cities As ICollection(Of City)
' and the city object passed into this method belongs to that collection...
RaisePropertyChanged(Function() city.Notes)
End If
End Sub
In C# I solved this with:
Dispatcher.CurrentDispatcher.Invoke(
new Action<Note>(item => city.Notes.Add(item)),
DispatcherPriority.Background,
note);

ListView selecting single item , wpf

i have a listview and it's items source is a list . I want a user to pick only one item . When I set SelectionMode of the listview to single , the user can still select several items and it seems like the listview is going crazy and selects items that user didn't select... looks very strange... can anyone know what could be the problem?
I cann't paste here a screenshot , i don't have the paste option.....
this is a xaml -
<StackPanel MinWidth="600" Margin="0,0,0,10" HorizontalAlignment="Left" Width="600">
<GroupBox Header="Command Queue" BorderThickness="0" Foreground="CornflowerBlue">
<Border BorderThickness="1.5" CornerRadius="10">
<ListView SelectionMode="Single" Background="Transparent" BorderThickness="0" Margin="5" Name="ListView_CmdQ" ItemsSource="{Binding}" MaxHeight="450" FontFamily="verdana" FontSize="12">
</ListView>
</Border>
</GroupBox>
</StackPanel>
Do the items in your list appear more than once? I've seen this problem before where you have something like this:
var a = new Thing();
var b = new Thing();
var myList = new List<Thing>();
myList.Add(a);
myList.Add(b);
myList.Add(a);
myList.Add(b);
If you were to bind a ListView to the myList, you'd get the behaviour you've described. I think basically it's to do with the fact that multiple items in the list match the SelectedItem, so the styling of the list gets a bit confused. One way around it is to wrap each item in another class:
var myList = new List<WrappedThing>();
myList.Add(new WrappedThing((a));
myList.Add(new WrappedThing((b));
myList.Add(new WrappedThing((a));
myList.Add(new WrappedThing((b));
... which means that each item in the list is unique, even though the item they're wrapping may not be.
If your list_listItems contains the same string twice, you get this behavior. This happens with value types and reference strings. You should probably wrap each string in a TextBlock and put that in the listview.
It looks like this is reported as a bug still active (since 2007) here.

WPF container for Image template

I'm storing the urls to the images in a sql ce 3.5 database as strings. I want to retrieve the urls and display them in the main application window. Here is the code:
DataSet myDataSet;
private void OnInit(object sender, EventArgs e)
{
string connString = Properties.Settings.Default.SystemicsAnalystDBConnectionString;
OleDbConnection conn = new OleDbConnection(connString);
OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT url FROM Library;", conn);
myDataSet = new DataSet();
adapter.Fill(myDataSet, "Library");
myListBox.DataContext = myDataSet;
}
The first problem is that I don't think the method onInit is fired. But I don't know the reason for that.
The second problem is with XAML file. I need a container for images (like the listbox for textboxes) and since I won't know how many images are there I need some kind of a template:
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=url}" />
</StackPanel>
</DataTemplate>
But there has to be some kind of a container that would have the datacontext set to the data source.
Could anyone help?
You can customize a listbox in wpf quite easily to have images in it, instead of text. Use the ItemTemplate or if you want to change to control itself, the ControlTemplate.
<ListBox ItemsSource="{Binding Library}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Path=url}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The DataContext for the ListBox should be your DataSet. You can use OnLoad instead of OnInit
Anyway I dont recommend the DataSet binding, it would be more managable if you create ViewModel class for your Library and create a collection of Library entities

Resources