Sam Hobbs

Sam Hobbs

  • 55
  • 29.2k
  • 2.1m

Automatic refresh of a WPF ListBox for CRUD using EF

Oct 15 2023 11:43 PM

Using Entity Framork I have a many-to-many relationship. In WPF I have a form for creating items. The problem is that when I add sub-items the list is not updated automatically.

For the purposes of a sample I have the following that uses an in-memory SQLite database. The database has tables for posts and tags. The window just shows:

  1. a ListBox of available tags
  2. a ListBox of selected tags
  3. a second ListBox of selected tags for demonstration purposes

And a button for adding a tag. When an available tag is selected and the Add button is pushed, the tag is added to the third ListBox but not the second ListBox. Note that in the button click handler, I set the ItemsSource for the third ListBox to null then reset it. Since that works, I know that the tags are being added to the Tags collection but the second ListBox is just not being updated. How do I get it to be updated automatically?

The following is the DbContext and entities.

Code Snippet

internal class db : DbContext
{
    public db(DbContextOptions options) : base(options) { }

    public virtual DbSet<Post> Posts { get; set; }
    public virtual DbSet<Tag> Tags { get; set; }
}

public class Post
{
    public event PropertyChangedEventHandler? PropertyChanged;
    public long Id { get; set; }
    public string? Name { get; set; }
    public List<Tag>? Tags { get; set; } = new List<Tag>();
}   

public class Tag : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    public long Id { get; set; }
    private string? title = string.Empty;
    public List<Post> Posts { get; set; } = new List<Post>();

    public Tag(string title)
    {
        this.title = title;
    }

    public string? Title
    {
        get { return title; }
        set
        {
            if (value != title)
            {
                title = value;
                NotifyPropertyChanged();
            }
        }
    }

    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

The following is the XAML.

Code Snippet

<Window.Resources>
    <DataTemplate x:Key="TagsTemplate">
        <TextBlock Text="{Binding Path=Title, Mode=TwoWay}"/>
    </DataTemplate>
</Window.Resources>
<Grid x:Name="theGrid">
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="20,20,20,20">
        <ListBox x:Name="AvailableTags"
                ItemTemplate="{StaticResource TagsTemplate}"
                IsSynchronizedWithCurrentItem="True"/>
        <Button x:Name="AddButton" Height="20" Margin="20,20,20,20"
               Click="AddButton_Click">Add</Button>
        <ListBox x:Name="SelectedTags" MinWidth="50"
                ItemTemplate="{StaticResource TagsTemplate}"
                IsSynchronizedWithCurrentItem="True"
                ItemsSource="{Binding Path=Tags, Mode=TwoWay}"/>
        <ListBox x:Name="SelectedTagsTest" MinWidth="50"
                ItemTemplate="{StaticResource TagsTemplate}"
                IsSynchronizedWithCurrentItem="True" Margin="20,0,0,0"
                ItemsSource="{Binding Path=Tags, Mode=TwoWay}"/>
    </StackPanel>
</Grid>

The following is the code-behind.

Code Snippet

public partial class MainWindow : Window
{
    db? db = null;
    Post post = new Post();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        var conn = new SqliteConnection("DataSource=:memory:");
        conn.Open();
        var options = new DbContextOptionsBuilder<db>()
            .UseSqlite(conn)
            .Options;
        //
        db = new db(options);
        try
        {
            db.Database.EnsureCreated();
        }
        catch (Exception ex)
        {
            MessageBox.Show($"Creation error: {ex.Message}");
            return;
        }
        //
        theGrid.DataContext = post;
        db.Tags.Add(new Tag("Hobby"));
        db.Tags.Add(new Tag("Profession"));
        db.Tags.Add(new Tag("Housework"));
        db.Tags.Add(new Tag("Animals"));
        db.Tags.Add(new Tag("Shopping"));
        db.Tags.Add(new Tag("Computers"));
        //
        AvailableTags.ItemsSource = db.Tags.Local.ToObservableCollection();
    }

    private void AddButton_Click(object sender, RoutedEventArgs e)
    {
        Tag? cat = AvailableTags.SelectedItem as Tag;
        if (cat == null)
            return;
        if (cat.Title == null)
            return;
        post.Tags.Add(new Tag(cat.Title));
        SelectedTagsTest.ItemsSource = null;
        SelectedTagsTest.ItemsSource = post.Tags;
    }
}

 


Answers (2)