How to bind EDM to WPF ListBox? - wpf

I'm trying to figure out WPF binding to SQLite.
I have an ADO.NET Entity Data Model generated to represent my SQLite database. The database only holds one table "People" with two columns "person_id" and "person_name". Now, I generated EDM classes for that table within my WPF Application.
I'm trying to bind to a list box. I can delete items from source and see it updating the list box. But I can't add items to the source using a text box and see it update the list box.
I declared data entities in Window1 class like this:
private static MyNewSqliteDbEntities2 _myEntities = new MyNewSqliteDbEntities2();
I have a list box that is bound to the ObjectQuery in the Window_Loaded event handler like this:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
peopleListBox.ItemsSource = _myEntities.People;
}
I have another text box which I use to add people by clicking on button. And I can delete Items by selecting an item in the list box and clicking delete button. Changes are committed to the database when a commit button is clicked. Please consider the code below:
private void addButton_Click(object sender, RoutedEventArgs e)
{
if (addPersonTextBox.Text != "")
{
People newPerson = new People();
newPerson.person_name = addPersonTextBox.Text;
//_myEntities.AddToPeople(newPerson);
_myEntities.AddObject("People", newPerson);
addPersonTextBox.Text = "";
}
}
private void deleteButton_Click(object sender, RoutedEventArgs e)
{
_myEntities.DeleteObject(peopleListBox.SelectedItem);
}
private void commitButton_Click(object sender, RoutedEventArgs e)
{
_myEntities.SaveChanges();
}
I tried refreshing the list box control using another button called "Refresh" in the following manner but with no luck (although, when I step through the code I see the source is updated):
private void refreshButton_Click(object sender, RoutedEventArgs e)
{
peopleListBox.ItemsSource = null;
peopleListBox.ItemsSource = _myEntities.People;
}
Here is the XAML code if you are wondering:
&ltWindow x:Class="BindingToSqLite.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="400" Width="400" Loaded="Window_Loaded"&gt
&ltWindow.Resources&gt
&ltDataTemplate x:Key="personNameTemplate"&gt
&ltTextBlock Text="{Binding Path=person_name}"/&gt
&lt/DataTemplate&gt
&lt/Window.Resources&gt
&ltGrid&gt
&ltGrid.ColumnDefinitions&gt
&ltColumnDefinition Width="190*" /&gt
&ltColumnDefinition Width="94*" /&gt
&ltColumnDefinition Width="94*" /&gt
&lt/Grid.ColumnDefinitions&gt
&ltGrid.RowDefinitions&gt
&ltRowDefinition Height="182*" /&gt
&ltRowDefinition Height="38*" /&gt
&ltRowDefinition Height="38*" /&gt
&ltRowDefinition Height="32*" /&gt
&lt/Grid.RowDefinitions&gt
&ltListBox Margin="5" Name="peopleListBox" Grid.ColumnSpan="3" ItemTemplate="{StaticResource personNameTemplate}" /&gt
&ltTextBox Grid.Row="1" Grid.ColumnSpan="2" Margin="5,10" Name="addPersonTextBox" /&gt
&ltButton Grid.Column="2" Grid.Row="1" Margin="5" Name="addButton" Click="addButton_Click"&gtAdd&lt/Button&gt
&ltButton Grid.Row="2" Margin="5" Name="commitButton" Click="commitButton_Click"&gtCommit&lt/Button&gt
&ltButton Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="2" Margin="5" Name="deleteButton" Click="deleteButton_Click"&gtDelete&lt/Button&gt
&ltButton Grid.Row="3" Margin="5" Name="refreshButton" Click="refreshButton_Click"&gtRefresh&lt/Button&gt
&lt/Grid&gt
&lt/Window&gt
I'm not sure if I'm doing this completely wrong. Any help is appreciated.

The People property needs to be an ObservableCollection<T> for this to work.

Related

WPF event unsubscribe leak in DataTemplate (XAML) on NET Framework

Imagine a UserControl with a ListBox having a CheckBox in a DataTemplate. The ItemsSource for the ListBox is some global list. The CheckBox has Checked/Unchecked events attached to it.
<ListBox ItemsSource="{Binding Source={x:Static a:MainWindow.Source}}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type a:Data}">
<CheckBox Content="{Binding Path=Name}"
Checked="ToggleButton_OnChecked"
Unchecked="ToggleButton_OnUnchecked"
IsChecked="{Binding Path=IsEnabled}"
Padding="10"
VerticalContentAlignment="Center"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I am logging loaded/unloaded/checked/unchecked events in the main window's log.
private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
Log("Checked");
}
private void ToggleButton_OnUnchecked(object sender, RoutedEventArgs e)
{
Log("Unchecked");
}
private void UserControl1_OnLoaded(object sender, RoutedEventArgs e)
{
Log("Loaded");
}
private void UserControl1_OnUnloaded(object sender, RoutedEventArgs e)
{
Log("Unloaded");
}
The main window features a dynamic list of UserControl1 instances (starting with just one). There are add/remove buttons that allow me to add more instances.
<UniformGrid Rows="2">
<DockPanel>
<Button DockPanel.Dock="Top" Click="Add">Add</Button>
<Button DockPanel.Dock="Top" Click="Remove">Remove</Button>
<ListBox x:Name="ListBox">
<local:UserControl1 />
</ListBox>
</DockPanel>
<ListBox ItemsSource="{Binding ElementName=This,Path=Log}" FontFamily="Courier New"/>
</UniformGrid>
The window's codebehind:
private void Add(object sender, RoutedEventArgs e)
{
ListBox.Items.Add(new UserControl1());
}
private void Remove(object sender, RoutedEventArgs e)
{
if (ListBox.Items.Count == 0) return;
ListBox.Items.RemoveAt(0);
}
When I run the app there is just one UserControl1 instance. If I add one more and then immediately remove one of them, then click the one and only checkbox on the screen, I see two "Checked" events logged. If I now uncheck it, there are two "Unchecked" events (even though "Unloaded" event was previously clearly logged. The hex numbers on the left show the output of a GetHashCode() which clearly shows the events were handled by distinct UserControl1 instances.
So even though one of UserControl1 gets unloaded, the events don't seem to get unsubscribed automatically. I have tried upgrading to NET Framework 4.8 to no avail. I see the same behavior. If I add 10 new controls and remove them immediately, I will observe 10 "Checked" or "Unchecked" events.
I have tried searching for a similar problem but could not find it. Is there something I am missing or did I just encounter a bug? Looking for workarounds.
Full source code is available on GitHub. https://github.com/wpfwannabe/datacontext-event-leak
In the MVVM pattern, the view is bind to the view model and doesn't survive it when the garbage collection do it's job.
In the example you provided, the view model is a static object and by definition can't be garbage collected, so the view can't neither be garbage collected.
There is no automatic unbinding since you can reuse an instance of an user control (it can be Loaded and UnLoaded multiple times).
The simplest¹ way to fix this memory leak is to do the unbinding on unload :
Wpf
// First give the ListBox a name
<ListBox x:Name="ListBox" ItemsSource="{Binding Source={x:Static a:MainWindow.Source}}">
Code behind
private void UserControl1_OnUnloaded(object sender, RoutedEventArgs e)
{
Log("Unloaded");
ListBox.ItemsSource = null;
}
1: The proper way is to wrap the static list in a dedicated list view model, make the view model disposable (to unbind the wrapper from the static list on dispose), dispose the view model on remove.

WPF Master-Details New data disappears from datagrid

I am working with a WPF project which in one window includes a pair of datagrids bound to an ADO dataset which includes two tables in a Master-Details relationship. The dataset is configured with the datarelation set to 'cascade'. Displaying data works fine; selecting a row in the Master grid displays the appropriate rows in the Detail grid.
However, adding a new row in the Master grid followed by one or more rows in the Detail grid then saving, results in the new Detail row(s) disappearing from the grid.
Breaking and examining the dataset in the visualiser indicates the data is correct; the PK and FK have been updated from the database correctly. Re-running the program displays the data correctly and examining the dataset appears to display identical values to the ones when the data was not displayed.
Saving using a TableAdapterManager or using separate Update calls on the tableadapters makes no difference. In fact, searching for this on the web has led me to unanswered questions that indicate the same thing happens with EF too, while doing the same thing in Winforms works correctly - so it would appear to be a WPF issue. Any ideas guys?
Code to demonstrate this, using Northwnd:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
MasterDetailTest.dsOrders dsOrders = ((MasterDetailTest.dsOrders)(this.FindResource("dsOrders")));
// Load data into the table Orders. You can modify this code as needed.
MasterDetailTest.dsOrdersTableAdapters.OrdersTableAdapter dsOrdersOrdersTableAdapter = new MasterDetailTest.dsOrdersTableAdapters.OrdersTableAdapter();
dsOrdersOrdersTableAdapter.Fill(dsOrders.Orders);
System.Windows.Data.CollectionViewSource ordersViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("ordersViewSource")));
ordersViewSource.View.MoveCurrentToFirst();
// Load data into the table Order_Details. You can modify this code as needed.
MasterDetailTest.dsOrdersTableAdapters.Order_DetailsTableAdapter dsOrdersOrder_DetailsTableAdapter = new MasterDetailTest.dsOrdersTableAdapters.Order_DetailsTableAdapter();
dsOrdersOrder_DetailsTableAdapter.Fill(dsOrders.Order_Details);
System.Windows.Data.CollectionViewSource ordersOrder_DetailsViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("ordersOrder_DetailsViewSource")));
ordersOrder_DetailsViewSource.View.MoveCurrentToFirst();
}
private void cmdSave_Click(object sender, RoutedEventArgs e)
{
MasterDetailTest.dsOrders dsOrders = ((MasterDetailTest.dsOrders)(this.FindResource("dsOrders")));
dsOrdersTableAdapters.TableAdapterManager tam = new dsOrdersTableAdapters.TableAdapterManager();
MasterDetailTest.dsOrdersTableAdapters.OrdersTableAdapter taOrders = new dsOrdersTableAdapters.OrdersTableAdapter();
MasterDetailTest.dsOrdersTableAdapters.Order_DetailsTableAdapter taDetail = new dsOrdersTableAdapters.Order_DetailsTableAdapter();
tam.OrdersTableAdapter = taOrders;
tam.Order_DetailsTableAdapter = taDetail;
tam.UpdateAll(dsOrders);
//taOrders.Update(dsOrders.Orders);
//taDetail.Update(dsOrders.Order_Details);
}
}
xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MasterDetailTest" x:Class="MasterDetailTest.MainWindow"
Title="MainWindow" Height="425" Width="912" Loaded="Window_Loaded">
<Window.Resources>
<local:dsOrders x:Key="dsOrders"/>
<CollectionViewSource x:Key="ordersViewSource" Source="{Binding Orders, Source={StaticResource dsOrders}}"/>
<CollectionViewSource x:Key="ordersOrder_DetailsViewSource" Source="{Binding FK_Order_Details_Orders, Source={StaticResource ordersViewSource}}"/>
</Window.Resources>
<Grid DataContext="{StaticResource ordersViewSource}">
<DataGrid HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="309" Width="440" ItemsSource="{Binding}"/>
<DataGrid x:Name="dgDetail" HorizontalAlignment="Left" Margin="455,10,0,0" VerticalAlignment="Top" Height="309" Width="439" ItemsSource="{Binding Source={StaticResource ordersOrder_DetailsViewSource}}"/>
<Button x:Name="cmdSave" Content="Save" HorizontalAlignment="Left" Margin="819,350,0,0" VerticalAlignment="Top" Width="75" Click="cmdSave_Click"/>
</Grid>

I am not able to set DataContext from code behind of one userControl to another userControl

I am not able to set DataContext from code behind of one userControl to another userControl .
private void grdWorkingList_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) {
DashboardSynopsisViewModel dsViewModel = new DashboardSynopsisViewModel();
AuditInfoViewModel auditInfoViewModel = new AuditInfoViewModel();
AuditInfoView auditInfoView = new AuditInfoView();
var selectedItem = (grdWorkingList.SelectedItem as AutoMgmtSoln.AuditWinPro.ClientData.Model.AuditDTO);
// MainWindow mainWindow = new MainWindow();
DSViewContentControl.Content = new AuditInfoView();
auditInfoView.DataContext = auditInfoViewModel;
auditInfoViewModel.AuditDTO = auditInfoViewModel.getAuditById(selectedItem.AuditId);
}
I have two user controls DashboardSynopsisView and AuditInfoView having view models DashboardSynopsisViewModel and AuditInfoViewModel. So in the code behind I have a event grdWorkingList_MouseDoubleClick which is fired on mouseDouble click which sets the content control of the dashboardSynopsisView to the AuditInfoView along with it's DataContext to AuditInfoViewModel.
AuditInfoViewModel has a property AuditDTO which I am using to display information of the selected item.
Here is part of my .xaml file
<TextBlock Grid.Column="0" Grid.Row="0" Text="Company Code :"></TextBlock>
<TextBlock Grid.Column="1" Grid.Row="0" Width="auto" Text="{Binding AuditDTO.CompanyCode}" ></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" >Company Name :</TextBlock>
<TextBlock Grid.Column="1" Grid.Row="1" Width="auto" Text="{Binding AuditDTO.CompanyName}" ></TextBlock>
Here is the change I have made to resolve my the problem .
private void grdWorkingList_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
DashboardSynopsisViewModel dsViewModel = new DashboardSynopsisViewModel();
AuditInfoViewModel auditInfoViewModel = new AuditInfoViewModel();
AuditInfoView auditInfoView = new AuditInfoView();
var selectedItem = (grdWorkingList.SelectedItem as AutoMgmtSoln.AuditWinPro.ClientData.Model.AuditDTO);
// MainWindow mainWindow = new MainWindow();
DSViewContentControl.Content = ***auditInfoView***;
auditInfoView.DataContext = auditInfoViewModel;
auditInfoViewModel.AuditDTO = auditInfoViewModel.getAuditById(selectedItem.AuditId);
}
You are setting the "auditInfoView" variable's data context, but setting the content control to a new AuditInfo view.
Make these match (probably by changing the content set to the auditInfoView variable) and your code should work.
To provide a little background, in case you are new to C#, using the new operator causes the class's constructor to be invoked, which creates a new object with default values (plus any sets or operations done by the constructor itself). Setting another instance's DataContext property will have no effect on the newly constructed instance.

databinding in silverlight

I have a xaml with a button like this:
Button.xaml
<Grid x:Name="LayoutRoot" >
<StackPanel >
<Button Content="Button1" Click="Button1_Click" />
</StackPanel >
</Grid>
and Button.xaml.cs:
private void Button1_Click(object sender, RoutedEventArgs e)
{
// Get a instance of ClientOversikt
CustomerView childWindow = m_container.Resolve<CustomerView >();
childWindow.Show();
}
It's working fine. But I want to use Databinding in Button.xaml instead of Click="Button1_Click". How could I do it?
I appreciate all the help
Since you're using Silverlight 4, you can use commands. You bind the Command property of the Button to an instance of ICommand, which will open the child window when executed. Then, when you click on the button, the command will be executed.
This page contains a reasonably good introduction to commanding.

Trapping events within list box item templates in WPF

I've got listbox that employs an item template. Within each item in the list, as defined in the template there is a button. When the user clicks the button I change a value in the data source that defines the sort order of the list. Changing the datasource is not a problem as this is working just fine within my application template.
However my next step is to reload the listbox with the new sorted data source. I've tried doing this from the tempalte but it apparently doesn't have access (or I can't figure out how to get access) to the parent elements so I can reset the .ItemSource property with a newly sorted data source.
Seems like this is possible but the solution is eluding me :(
You could use databinding to bind the Button's Tag to its ListBox ancestor. Example:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="myDataTemplate">
<Button Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"
Click="Button_Click">MyButton</Button>
</DataTemplate>
</Grid.Resources>
<ListBox ItemTemplate="{StaticResource myDataTemplate}" ItemsSource="..." />
</Grid>
And here's the codebehind:
private void Button_Click(object sender, RoutedEventArgs e)
{
ListBox myListBox = (ListBox)((Button)sender).Tag;
...do something with myListBox...
}
Alternatively, you can manually climb the Visual Tree upwards in your code (no Tag data binding needed):
private void Button_Click(object sender, RoutedEventArgs e)
{
DependencyObject search = (DependencyObject)sender;
while (!(search is ListBox)) {
search = VisualTreeHelper.GetParent(search);
}
ListBox myListBox = (ListBox)search;
...do something with myListBox...
}

Resources