WPF Automatic binding when an EF's entity values change - c#

I am new to Entity Framework and I'm trying to learn it.
I was trying to modify an exercise found on the official documentation: I would like to have a list of fathers and a list of sons. Every son must have a father selected from a combobox menu.
Now I can do that but, if I add a father, I don't see it in the father list nor in the combobox. If I add a son I don't see the son in the son's list.
If I close and reopen the program I correctly see fathers and sons previously added.
How I can automatically upgrade data in combobox and in the listview?
I don't like to call a function to refresh, I would like to automatically make the refresh when changing something in the database.
My project is made of 3 files:
MainWindow.xaml
<Window x:Class="EF7Fam.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:EF7Fam"
mc:Ignorable="d"
Loaded="Page_Loaded"
Title="MainWindow" Height="350" Width="550">
<Grid>
<StackPanel Orientation="Horizontal">
<StackPanel Width="263" Margin="3 0 3 0">
<TextBox Name="NewFT"></TextBox>
<Button Click="Add_Click">Add</Button>
<ListView Name="Fathers">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<StackPanel Width="263" Margin="3 0 3 0">
<TextBox Name="NewSN"></TextBox>
<ComboBox Name="FT" DisplayMemberPath="Name" SelectedValuePath="FTId"/>
<Button Click="Add_SN_Click">Add</Button>
<ListView Name="Sons">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using Microsoft.Data.Entity;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace EF7Fam
{
/// <summary>
/// Logica di interazione per MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
using (var db = new Family())
{
db.Database.Migrate();
}
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
Fathers.ItemsSource = db.Fathers.ToList();
Sons.ItemsSource = db.Sons.ToList();
FT.ItemsSource = db.Fathers.ToList();
}
}
private void Add_Click(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
var ft = new Father { Name = NewFT.Text };
db.Fathers.Add(ft);
db.SaveChanges();
}
}
private void Add_SN_Click(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
var sn = new Son { Name = NewSN.Text, FTId = Int32.Parse(FT.SelectedValue.ToString()) };
db.Sons.Add(sn);
db.SaveChanges();
}
}
}
}
Model.cs
using System;
using Microsoft.Data.Entity;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Runtime.CompilerServices;
namespace EF7Fam
{
public class Family : DbContext
{
public DbSet<Father> Fathers { get; set; }
public DbSet<Son> Sons { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite($"Filename=Family.db");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Father>()
.Property(b => b.Name)
.IsRequired();
}
}
public class Father : INotifyPropertyChanged
{
[Key]
public int FTId { get; set; }
public string Name { get; set; }
public List<Son> Sons { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Son : INotifyPropertyChanged
{
[Key]
public int SNId { get; set; }
public string Name { get; set; }
public int FTId { get; set; }
public Father Father { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I looked many times on this site and in the Web, but without finding a solution.
I will be very grateful if someone can help me to let me know what I doing wrong,
Thanks,
bye,
Enrico

To bound your controls to the DbSets you need to do this:
private void Page_Loaded(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
db.Fathers.Load();
Fathers.ItemsSource = db.Fathers.Local;
db.Sons.Load();
Sons.ItemsSource = db.Sons.Local;
FT.ItemsSource = db.Fathers.Local;
}
}
Calling Load method you are going to load the existing objects into memory and DbSet<TEntity>.Local property will give you an ObservableCollection<TEntity> that contains all Unchanged, Modified and Added objects that are currently tracked by the DbContext for the given DbSet. Adding or Removing from the ObservableCollection will also perform the corresponding Add/Remove on the DbSet.
This way if you need to save changes after perform all the operations that you need in your view, you can define a command or override the click event of a button in your view and call the SaveChanges method of your context:
private void SaveChanges_Click(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
db.SaveChanges();
}
}
Update
Now I saw that you are using EF7, well the true is I'm not aware about all the changes in this version, but I can tell you that there are a lot, maybe this property is not still implemented. I found a question asking about that and is not answered yet.
Digging more in documentation I think I found a solution but the true is I don't like it, but it could work until the Load property shows up. According to the EF documentation you can do this:
private void Add_Click(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
var ft = new Father { Name = NewFT.Text };
db.Fathers.Add(ft);
db.SaveChanges();
Fathers.ItemsSource = db.Fathers.ToList();
}
}
If I find a better solution I'll let you know.

Related

How do I bind an event captured by a XAML element to a method in an arbitrary class in C#?

I am a C# newbie trying to build a simple MVVM app, and I am having trouble tying events in my XAML View to methods in my Model or ViewModel. I understand why MVVM is used and feel like I get the broad strokes of how to put an MVVM app together, but I am lost in the details. I apologize in advance if it looks like I have no idea what I'm doing, but I don't, despite lots of reading up on the subject.
I want btnUpdate_Click in MainScreenViewModel to execute when the button is clicked, but I get the error
MC6005 Click="vm:btnUpdate_Click" is not valid. 'vm:btnUpdate_Click' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid.
If my classes are public and in the same namespace, what do I need to do to make them visible from my View? I don't want to move methods back to the MainWindow class.
<Window x:Class="SFM_Calculator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SFM_Calculator"
xmlns:vm="MainScreenViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
>
<Window.DataContext>
<local:SFMModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
...
<ColumnDefinition/>
</Grid.ColumnDefinitions>
...
</Grid.RowDefinitions>
...
<Button
Grid.Column="1"
Grid.Row="2"
x:Name="btnUpdate"
Content="Update"
Click="vm:btnUpdate_Click"
/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Linq;
using System.ComponentModel;
namespace SFM_Calculator
{
public class MainScreenViewModel : INotifyPropertyChanged
{
private void btnUpdate_Click(object sender, System.Windows.RoutedEventArgs e)
{
TestInt = 999;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public SFMModel sfmModel = new SFMModel();
private int _testInt;
public int TestInt
{
get { return _testInt; }
set { _testInt = value; }
}
public MainScreenViewModel()
{
Debug.WriteLine("Got here.");
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace SFM_Calculator
{
public class SFMModel : INotifyPropertyChanged
{
private int _tprop;
public int TProp
{
get { return _tprop; }
set { _tprop = value; }
}
public SFMModel ()
{
TProp = 69;
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Configuration;
namespace SFM_Calculator
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
Your viewModel should expose a command, not a method. Plus you should access the exposed command via binding mechanism.
Sample Command that implements required ICommand interface
internal class Command : ICommand
{
private readonly Action execute;
public Command(Action execute)
{
this.execute = execute;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
execute();
}
}
Sample ViewModel that exposes ICommand (not a regular method as in your example). It will change the value of Text property after clicking the button - just to show that it works.
internal class ViewModel : INotifyPropertyChanged
{
public string Text { get; private set; }
public ICommand AwesomeCommand { get; }
public ViewModel()
{
AwesomeCommand = new Command(() => {
Text = "Button clicked";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
});
}
public event PropertyChangedEventHandler? PropertyChanged;
}
MainWindow:
<StackPanel>
<Button Command="{Binding AwesomeCommand}"></Button>
<Label Content="{Binding Text}" Height="100"></Label>
</StackPanel>
code-behind MainWindow, to hook-up ViewModel and the view (MainWindow):
public MainWindow()
{
DataContext = new ViewModel();
InitializeComponent();
}

C# WPF DataBinding - Data not Updating

There are several "WPF Databinding Not Updating" questions on here, and I've read through them all, including the top-ranked one:
WPF DataBinding not updating?
However, I'm swapping out entire objects in my situation, and what's worse is that when I try to create a reproducible version of what I'm doing, it works in that version:
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label Content="Contact Address" />
<TextBox x:Name="txtContactAddress" Text="{Binding Path=Contact.Address, Mode=OneWay}" />
<Label Content="Organization Address" />
<TextBox x:Name="txtOrgAddress" Text="{Binding Path=Organization.Address, Mode=OneWay}" />
<Button Content="Next Contact" Click="Button_Click" />
</StackPanel>
</Window>
Code-Behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public RecordWrapper CurrentRecord;
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
CurrentRecord = new RecordWrapper(new ContactData());
this.DataContext = CurrentRecord;
}
}
public class RecordWrapper : INotifyPropertyChanged
{
private ContactData _Contact;
public ContactData Contact
{
get { return _Contact; }
set
{
_Contact = value;
OnPropertyChanged("Contact");
Organization = _Contact.Organization;
}
}
private OrganizationData _Organization;
public OrganizationData Organization
{
get { return _Organization; }
set
{
_Organization = value;
OnPropertyChanged("Organization");
}
}
public RecordWrapper(ContactData contactData)
{
Contact = contactData;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
#endregion
}
public class ContactData
{
public string Address { get; set; }
public OrganizationData Organization { get; set; }
public ContactData()
{
Address = new Random().Next(1000, 9999) + " Contact Street";
Organization = new OrganizationData();
}
}
public class OrganizationData
{
public string Address { get; set; }
public OrganizationData()
{
Address = new Random().Next(1000, 9999) + " Org Street";
}
}
}
So this particular sample works as expected - when I click on the "Next Contact" button, it generates a new RecordWrapper object that contains a new ContactData and new OrganizationData object, and then assigns the new object to DataContext and then I see the textboxes update properly.
Since I'm replacing the entire object in the DataContext, I can even ditch the property notifications and reduce the RecordWrapper down to just:
public class RecordWrapper
{
public ContactData Contact { get; set; }
public OrganizationData Organization { get; set; }
public RecordWrapper(ContactData contactData)
{
Contact = contactData;
Organization = Contact.Organization;
}
}
...and it still works great.
My real code, however, reflects the first example.
In the real code, it seems like updating this.DataContext with a new object doesn't work. Nothing is updated, but there are no errors, either. Setting the path in XAML doesn't work, either. In fact, the only way I can get it to work is to use code-behind to set the bindings directly to the data object:
BindingOperations.SetBinding(txtContactAddress, TextBox.TextProperty, new Binding() { Source = this.RecordWrapper, Path = new PropertyPath("Contact.Address"), Mode = BindingMode.OneWay });
When I do this, I can see it working, but that's obviously missing the point of binding if I have to run code on each field each time to update the bindings.
I'm stumped because the real code is nearly identical to the example aside from having more fields, different field names, and differences that have nothing to do with data or binding. When I click on a button, this is the exact code:
this.Data = new DataBindings(CurrentContact.FetchFake());
this.DataContext = this.Data;
The FetchFake() method generates a new CurrentContact object with some fake data, and I have verified that after that first line runs, this.Data.CurrentContact.Login is populated and is a read-only property of the CurrentContact object (not a field).
...and one of the XAML textboxes looks like this:
<TextBox x:Name="txtAccountNumber" Grid.Column="1" Grid.Row="1" Width="Auto" Text="{Binding Path=CurrentContact.Login, Mode=OneWay}"/>
The result is blank. I have also tried variations on order (e.g. setting DataContext to this.Data first, then setting the this.Data.CurrentContact property and letting the property-changed notification kick off, but still no luck. But if I run:
BindingOperations.SetBinding(tabAccount.txtAccountNumber, TextBox.TextProperty, new Binding() { Source = this.Data, Path = new PropertyPath("CurrentContact.Login"), Mode = BindingMode.OneWay });
...then I'll see the value show up.
Based on these symptoms, and the fact that my first example works but the real code doesn't, can anyone think of any place I should look for some culprits?
Ughhhhh. Figured it out. I had a REALLY old line of code in there that changed the DataContext of a container element that was between the Window and the Textbox, which interrupted the correct binding. I thought I had removed all of it, but hadn't. It DID throw an error, as Erno de Weerd had suggested, but the error was lost among some of the startup logging. We can close this one out.

Data Grid control not showing ObservableCollection data

I'm rather new to WPF and C# in general. I'm playing around with it and encountered a problem which I feel like would be a piece of cake for an expert but I have no idea what I'm doing wrong.
I'm trying to create a simple DataGrid control (within a TabControl) and bind it to an ObservableCollection object.
I use microsoft's Data Binding Demo provided in their data binding overview as a basis for my code.
MainWindow XAML:
<Window x:Class="PetProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PetProject"
mc:Ignorable="d"
Title="PetProject" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=Dogs}"
x:Key="DogsDataView" />
</Window.Resources>
<Grid Margin="8,8,8,8">
<TabControl>
<TabItem Header="Dogs">
<DataGrid ItemsSource="{Binding Source={StaticResource DogsDataView}}">
</DataGrid>
</TabItem>
</TabControl>
</Grid>
</Window>
Code-Behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace PetProject
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
///
public partial class MainWindow : Window
{
CollectionViewSource DogsDataView;
public MainWindow()
{
InitializeComponent();
DogsDataView = (CollectionViewSource)(this.Resources["DogsDataView"]);
}
}
}
The App XAML is
<Application x:Class="PetProject.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PetProject"
Startup="AppStartup">
<!--StartupUri="MainWindow.xaml"-->
</Application>
code-behind:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace PetProject
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private ObservableCollection<Dog> dogs = new ObservableCollection<Dog>();
void AppStartup(object sender, StartupEventArgs args)
{
LoadData();
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
}
public ObservableCollection<Dog> Dogs
{
get { return this.dogs; }
set { this.dogs = value; }
}
private void LoadData() {
Dog Johnny = new Dog("Johnny",1325);
Dog Diamond = new Dog("Diamond",1327);
this.Dogs.Add(Johnny);
this.Dogs.Add(Diamond);
}
}
}
Dog is just a class implementing the INotifyPropertyChanged interface (which for now does not do anything):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace PetProject
{
public class Dog : INotifyPropertyChanged
{
private string name;
private string number;
public event PropertyChangedEventHandler PropertyChanged;
public Dog(string name, int number)
{
this.name = name;
this.number = number.ToString("D4");
}
protected void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
I'd appreciate any help in understanding why the DataGrid is not populated.
Also, any suggestion on bad coding habits or improvement to the code would be very welcome, as I'm in a very initial learning-by-experience phase.
Thanks!
You can't bind to private fields. You can only bind to public properties. As far as the DataGrid is concerned, Dog has no information to display.
public class Dog : INotifyPropertyChanged
{
private string _name;
private string _number;
public Dog(string name, int number)
{
Name = name;
Number = number.ToString("D4");
}
public String Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
NotifyPropertyChanged(nameof(Name));
}
}
}
public String Number
{
get { return _number; }
set
{
if (value != _number)
{
_number = value;
NotifyPropertyChanged(nameof(Number));
}
}
}
I'm prefixing your private fields with underscores because that's standard practice. It's standard practice because having two identifiers which differ only by case is a recipe for confusion and bugs.
First of all, I suggest you to read about MVVM principles then maybe choose a MVVM framework to use with WPF. For instance MVVM light toolkit is a good choice to start and understand MVVM.
For your example, here are just a few remarks about your code:
I suggest you to group all your 'business' data into a viewModel class (see MVVM practices all around the web) - nothing in the App class...
This ViewModel will implement 'INotifyPropertyChanged' interface
So the Dogs property will be located into this ViewModel and will raise the 'PropertyChanged' event in its setter (what is not currently the case in your sample)
There are several MVVM frameworks that would 'bind' automatically your views to your view model, but to understand, the main goal is to set your Window.DataContext with the appropriate ViewModel.
That's why you can restore in App.xaml: StartupUri="MainWindow.xaml"
Then to load your ViewModel, you can do something like that to load your Dogs collection:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// For test: LOAD & SET your DataContext here
//
var myDogViewmodel = new DogViewModel();
myDogViewModel.LoadData();
this.DataContext = myDogViewmodel;
}
}
Your ViewModel should look like something like that:
public class DogViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Dog> _dogs;
public ObservableCollection<Dog> Dogs
{
get { return _dogs; }
set
{
_dogs = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Dogs"));
}
}
public void LoadData()
{
// ....
}
}
Then your Dog class must also implement INotifuPropertyChanged interface:
public class Dog : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
private int _number;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
public int Number
{
get => _number;
set
{
if (_number != value)
{
_number = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Number"));
}
}
}
}
Finally,in your MainWindow.xaml:
>
<Window x:Class="PetProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PetProject"
mc:Ignorable="d"
Title="PetProject" Height="350" Width="525">
<Grid Margin="8,8,8,8">
<TabControl>
<TabItem Header="Dogs">
<DataGrid ItemsSource="{Binding Dogs}" />
</TabItem>
</TabControl>
</Grid>
It should work now ;) Tell me if it's clear. Get familiar with MVVM...

WPF C# ComboBox with multiple lines and accepting returns?

Is there an easy way to do a multiline combobox in WPF C#? What do I mean by this? I mean a textbox that supports multiple lines and carriage returns; where it word wraps... It needs to have a scrollbar so that if the text in the box is taller than the height of the combobox, the user can scroll down.
In addition, because its a combobox, it needs to have a dropdown button so that the user can quickly swap between groups of text. I've tried googling for this, but I can't find anyone talking about such a combobox.
EDITED FOR COMPLETE WORKING SOLUTION:
XAML:
<Window x:Class="delete.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox Height="30" Width="300" ItemsSource="{Binding items}" SelectedItem="{Binding item}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Width="250" Height="30" Text="{Binding Name}" VerticalScrollBarVisibility="Auto"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
Code-behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace delete
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
Setup();
this.DataContext = this;
}
ObservableCollection<Thang> _items;
public ObservableCollection<Thang> items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged("items");
}
}
private Thang _item;
public Thang item
{
get { return _item; }
set
{
_item = value;
OnPropertyChanged("item");
}
}
public void Setup()
{
items = new ObservableCollection<Thang>();
items.Add(new Thang("1", "One"));
items.Add(new Thang("2", "Two"));
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class Thang
{
public Thang(string id, string name)
{
Name = name;
ID = id;
}
public string Name { get; set; }
public string ID { get; set; }
}
}

Filtering animals using wpf radiobuttons

I am playing with a sample WPF application that is tiered in a Model-View-Presenter manner. The Model is a collection of Animals which is displayed in a view via binding to properties in a presenter class. The XAML has an items control that displays all the animals in a model.
The model class has a boolean attribute called 'IsMammal'. I want to introduce a filter in the XAML in the form of a radio button group that filters the collection of animals based on the 'IsMammal' attribute. Selection of the radiobutton 'Mammals' updates the items control with all the Animals that have the 'IsMammal' value set to true and when the value is toggled to 'Non-Mammals', the display is updated with all animals that have that particular boolean set to false. The logic to do the filtering is extremely simple. What is troubling me is the placement of the logic. I don't want any logic embedded in the *.xaml.cs. I want the toggling of the radiobutton to trigger a logic in the presenter that tapers my animal collection to be rebound to the display.
Some guidance here will be extremely appreciated.
Thanks
I suggest you do the following in your Presenter:
=> Create a ListCollectionView field. Set it to be equal to the "Default Collection View" of your collection, and use it as the ItemsSource for the list control. Something like:
public class Presenter()
{
private ListCollectionView lcv;
public Presenter()
{
this.lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(animalsCollection);
listControl.ItemsSource = this.lcv;
}
}
=> In the handler/logic for the RadioButton's corresponding event, filter the ListCollectionView. Something like:
void OnCheckedChanged()
{
bool showMammals = radioButton.IsChecked;
this.lcv.Filter = new Predicate((p) => (p as Animal).IsMammal == showMammals);
this.lcv.Refresh();
}
Hope this helps.
EDIT:
While doing this is possible using MVP, using MVVM should be a better choice, IMHO (and as mentioned by the other answer). To help you out, I wrote a sample that implements your requirements via MVVM. See below:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<RadioButton x:Name="rb1" GroupName="MyGroup" Content="IsMammal = true" Checked="rb1_Checked"/>
<RadioButton x:Name="rb2" GroupName="MyGroup" Content="IsMammal = false" Checked="rb2_Checked"/>
<ListBox ItemsSource="{Binding Path=AnimalsCollectionView}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Window>
Code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Threading;
using System.ComponentModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void rb1_Checked(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel).UpdateFilter(true);
}
private void rb2_Checked(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel).UpdateFilter(false);
}
}
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Animal> animals = new ObservableCollection<Animal>();
private ListCollectionView animalsCollectionView;
public ListCollectionView AnimalsCollectionView
{
get { return this.animalsCollectionView; }
}
public void UpdateFilter(bool showMammals)
{
this.animalsCollectionView.Filter = new Predicate<object>((p) => (p as Animal).IsMammal == showMammals);
this.animalsCollectionView.Refresh();
}
public ViewModel()
{
this.animals.Add(new Animal() { Name = "Dog", IsMammal = true });
this.animals.Add(new Animal() { Name = "Cat", IsMammal = true });
this.animals.Add(new Animal() { Name = "Bird", IsMammal = false });
this.animalsCollectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(this.animals);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
public class Animal : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
this.name = value;
this.OnPropertyChanged("Name");
}
}
private bool isMammal;
public bool IsMammal
{
get { return this.isMammal; }
set
{
this.isMammal = value;
this.OnPropertyChanged("IsMammal");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
}
I came from an MVP background before learning WPF and I have come to find that adapting the MVP pattern to WPF is a difficult exercise at best. The "proper" (read, least frustrating) approach is to utilize the Model-View-ViewModel (MVVM) pattern, which makes heavy use of the databinding features of WPF to minimize the amount of code that ends up the view-behind file.
In the simplest case, you would end up with two properties on your ViewModel:
bool FilterMammals
ObservableCollection MammalsToDisplay
In the XAML, You would bind the first to your radio button group and the second to the ItemsSource of the ListBox. The WPF databinding framework will call your property setter whenever the radiobutton group value changes, and in here you can filter and then update the list of items.
I'm not sure what your familiarity with MVVM is, so I'll stop here. Let me know if more detail would help :).

Categories

Resources