I am trying to create a wpf combobox containing a list of World of warcraft items. I am using a item template for the combo box so that the items icon and item name are shown in the combobox drop down list. It works as is and I can start typing a item name and the list of items in the popup are automatically filtered.
The problem is that when I click an item I want the item name to be shown in the comboboxes text area. Instead what I get is a very brief ItemDisplayModel.ToString() then a empty string.
The ItemDisplayModel.ToString() being the ItemDisplayModel type that I selected from the list. Which makes sense cause the combobox control ItemsSource property contains an array of ItemDisplayModel types.
But in my AuctionHouseFilterItemNameModel.Value property I am detecting weather the value has changed and if so rebuild the list of items to be displayed in the combobox. So because the Text in the combobox was changed to ItemDisplayModel.ToString() when I click an item in the popup list it again rebuilds the list of items only this time there is no match because ItemDisplayModel.ToString() does not match any known item names.
All I am trying to do is when I click an item in the popup list I want the ItemDisplayModel.Name to be mapped to the AuctionHouseFilterItemNameModel.Value property and have the combobox text area to display AuctionHouseFilterItemNameModel.Value as well.
Does that make sense? I've tried numerous different ways and I am at a loss. Looking around the web did'nt give me the answer either. I'm sure it's something simple to fix but the solution eludes me.
Here is my XAML for the control
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:AuctionHouseSearchFilters="clr-namespace:Codefarts.WowTracks.DataMinerWPF.Models.AuctionHouseSearchFilters"
xmlns:Models="clr-namespace:Codefarts.WowTracks.DataMinerAppCore.Models;assembly=Codefarts.WowTracks.DataMinerAppCore"
xmlns:DataMinerAppCore="clr-namespace:Codefarts.WowTracks.DataMinerAppCore;assembly=Codefarts.WowTracks.DataMinerAppCore"
xmlns:dataMinerWpf="clr-namespace:Codefarts.WowTracks.DataMinerWPF"
x:Name="userControl"
x:Class="Codefarts.WowTracks.DataMinerWPF.Controls.AuctionHouseSearchFilters.AuctionHouseItemNameControl"
mc:Ignorable="d"
d:DesignHeight="51" d:DesignWidth="283">
<UserControl.Resources>
<dataMinerWpf:UriToBitmapImageConverter x:Key="UriToImageConverter" />
<BitmapImage x:Key='defaultImage' UriSource='/Resources\118.png' />
<DataTemplate x:Key="ItemTemplate" DataType="Models:ItemDisplayModel" >
<StackPanel Orientation="Horizontal" Margin="0 5 0 5">
<Image Width="50" Height="50" Stretch="Fill" Source="{Binding IconUrl, Converter={StaticResource UriToImageConverter}, TargetNullValue={StaticResource defaultImage}}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Label Content="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<UserControl.DataContext>
<AuctionHouseSearchFilters:AuctionHouseFilterItemNameModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<ComboBox x:Name="ItemList" MaxDropDownHeight="592" Grid.Row="0" ItemsSource="{Binding ItemDisplayModels}" ItemTemplate="{StaticResource ItemTemplate}" IsEditable="True" IsTextSearchEnabled="True" IsTextSearchCaseSensitive="False" Text="{Binding Value}" >
</ComboBox>
</Grid>
</UserControl>
Here is my code behind
public partial class AuctionHouseItemNameControl : UserControl, IAuctionHouseFilterControl
{
private Application app;
public AuctionHouseItemNameControl()
{
InitializeComponent();
}
public void SetModel(IAuctionHouseSearchFilter model)
{
this.DataContext = model;
}
public void SetApplication(Application app)
{
this.app = app;
}
private void ItemList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//if (model.Handled)
//{
// return;
//}
// model.Handled = true;
if (e.AddedItems != null && e.AddedItems.Count > 0)
{
var model = this.DataContext as Models.AuctionHouseSearchFilters.AuctionHouseFilterItemNameModel;
var item = e.AddedItems[0] as ItemDisplayModel;
// this.ItemList.SelectedValue = item.Name;
model.Value = item.Name;
// this.ItemList.SelectedItem = item.Name;
}
// model.Handled = false;
}
}
and here is my data models
[Export(typeof(IAuctionHouseSearchFilter))]
public class AuctionHouseFilterItemNameModel : IAuctionHouseSearchFilter, INotifyPropertyChanged
{
private string value;
private bool connected;
private Application app;
private ItemDisplayModel[] displayItems;
private CancellationTokenSource backgroundCancel;
private bool buildingDisplayItems;
private readonly object lockObject = new object();
private ItemDisplayModel displayValue;
// private bool Handled { get; set; }
public ItemDisplayModel DisplayValue
{
get
{
return this.displayValue;
}
set
{
if (value == null)
{
return;
}
this.Value = value.Name;
this.displayValue = value;
this.OnPropertyChanged();
}
}
public string Value
{
get
{
return this.value;
}
set
{
//if (this.Handled)
//{
// return;
//}
if (value == this.value)
{
return;
}
this.value = value;
// this.Handled = true;
this.OnPropertyChanged();
var cancellationTokenSource = this.backgroundCancel;
if (cancellationTokenSource != null)
{
cancellationTokenSource.Cancel();
}
this.BuildDisplayItems();
// this.Handled = false;
}
}
public IEnumerable<AuctionDataModel> Filter(MainFilterModel model, IEnumerable<AuctionDataModel> items)
{
if (!this.connected)
{
return items;
}
// get item id from it's name
var list = this.app.ItemNames[model.SelectedRegion];
var id = list.FirstOrDefault(x => x.Name.ToLowerInvariant().Contains(this.value.Trim().ToLowerInvariant()));
return items.Where(x => id != null && x.ItemId == id.Id);
}
public IEnumerable<ItemDisplayModel> ItemDisplayModels
{
get
{
return this.displayItems;
}
}
public async void BuildDisplayItems()
{
if (this.buildingDisplayItems)
{
return;
}
if (!this.connected)
{
this.displayItems = null;
}
// return this.GetDisplayItems();
this.buildingDisplayItems = true;
this.backgroundCancel = new CancellationTokenSource();
this.OnPropertyChanged("ItemDisplayModels");
await Task.Factory.StartNew(
() =>
{
var originalItems = this.displayItems;
this.displayItems = new[] { new ItemDisplayModel() { Name = "Rebuilding list..." } };
var correctedSearchValue = this.value.Trim().ToLowerInvariant();
//lock (this.lockObject)
//{
this.displayItems = (string.IsNullOrWhiteSpace(this.value) ?
this.app.DisplayItemModels :
this.app.DisplayItemModels.Where(x => x.Name.ToLowerInvariant().Contains(correctedSearchValue))).Take(100).AsParallel().ToArray();
this.buildingDisplayItems = false;
this.OnPropertyChanged("ItemDisplayModels");
},
this.backgroundCancel.Token);
}
public string Name
{
get
{
return "Item Name";
}
}
public Type Control
{
get
{
return typeof(AuctionHouseItemNameControl);
}
}
public virtual IAuctionHouseSearchFilter Clone()
{
return new AuctionHouseFilterItemNameModel()
{
Value = this.Value
};
}
public void Connect(Application app)
{
if (this.connected)
{
return;
}
this.app = app;
this.connected = true;
}
public void Disconnect()
{
if (!this.connected)
{
return;
}
this.app = null;
this.connected = false;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ItemDisplayModel : INotifyPropertyChanged
{
private string iconUrl;
private string region;
private string name;
private int itemId;
public int ItemId
{
get
{
return this.itemId;
}
set
{
if (value == this.itemId)
{
return;
}
this.itemId = value;
this.OnPropertyChanged();
}
}
public string Name
{
get
{
return this.name;
}
set
{
if (value == this.name)
{
return;
}
this.name = value;
this.OnPropertyChanged();
}
}
public string Region
{
get
{
return this.region;
}
set
{
if (value == this.region)
{
return;
}
this.region = value;
this.OnPropertyChanged();
}
}
public string IconUrl
{
get
{
return this.iconUrl;
}
set
{
if (value == this.iconUrl)
{
return;
}
this.iconUrl = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The problem is that when I click an item I want the item name to be shown in the comboboxes text area. Instead what I get is a very brief ItemDisplayModel.ToString() then a empty string.
To get the Name property displayed in the editable text area of the ComboBox use TextSearch.TextPath so your ComboBox definition would look like:
<ComboBox x:Name="ItemList"
Grid.Row="0"
IsEditable="True"
IsTextSearchCaseSensitive="False"
IsTextSearchEnabled="True"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding ItemDisplayModels}"
MaxDropDownHeight="592"
TextSearch.TextPath="Name" />
Now when an item is selected from the dropdown instead of seeing ItemDisplayModel.ToString(), you will see the Name property.
Related
new to WPF and programming here I am trying to update an item in a ListView when a button is pushed if the item already exists in that ListView, for example a user inputs CAR and CAR is already in the ListView the CAR count should go from 1 to 2.
This is my InventoryItem class:
public class InventoryItem
{
public string Name { get; set; }
public int Count { get; set; }
}
Here is my ViewModel
public class ViewModel : ViewModelBase
{
private InventoryItem _item;
private int _count;
private ObservableCollection<InventoryItem> _inventoryItems;
private ICommand _addItem;
public InventoryItem Item
{
get
{
return _item;
}
set
{
_item = value;
NotifyPropertyChanged("Item");
}
}
public int Count
{
get { return _count; }
set { _count = value; NotifyPropertyChanged("Count"); }
}
public ObservableCollection<InventoryItem> InventoryItems
{
get
{
return _inventoryItems;
}
set
{
_inventoryItems = value;
NotifyPropertyChanged("Items");
}
}
public ICommand AddItem
{
get
{
if (_addItem == null)
{
_addItem = new RelayCommand(ParamArrayAttribute => this.Submit(), null);
}
return _addItem;
}
}
public ViewModel()
{
Item = new InventoryItem();
InventoryItems = new ObservableCollection<InventoryItem>();
InventoryItems.CollectionChanged += new NotifyCollectionChangedEventHandler(InventoryItems_CollectionChanged);
}
void InventoryItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Items");
NotifyPropertyChanged("Count");
}
private void Submit()
{
if (InventoryItems.Any(p => p.Name == Item.Name))
{
InventoryItems.First(x => x.Name == Item.Name).ItemCount++;
}
else
{
InventoryItems.Add(Item);
}
}
}
Here is my View
<ListView x:Name="listBox" ItemsSource="{Binding InventoryItems}" Grid.Row="0" HorizontalAlignment="Stretch" Height="Auto" Margin="10,10,10,60" VerticalAlignment="Stretch">
<ListView.Resources>
<Style TargetType="GridViewColumnHeader">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Name}" Width="Auto"/>
<GridViewColumn DisplayMemberBinding="{Binding ItemCount}" Width="Auto"/>
</GridView>
</ListView.View>
</ListView>
Whenever an item that exists in the list is typed in it appears that the InventoryItems ObservableCollection is being updated however InventoryItems_CollectionChanged event is not being fired so the ListView is not being updated. Isn't the collection changing so that event should be fired to update the ListView or am I not understanding the Binding and the event?
You need to notify about property changes in the InventoryItem class to update the changes of ItemCount in the ListView.
Quick solution:
public class InventoryItem : ViewModelBase
{
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
NotifyPropertyChanged(nameof(Name));
}
}
}
private string _name;
public int ItemCount
{
get { return _itemCount; }
set { _itemCount = value; NotifyPropertyChanged(nameof(ItemCount));
}
}
private int _itemCount;
}
}
The ObservableCollection class already contains handling of INotifyCollectionChanged.
You only need this line for the ObservableCollection. You can remove everything else that has to do with "InventoryItems" and the collection in your ViewModel.
public ObservableCollection<InventoryItem> InventoryItems { get; } = new ObservableCollection<InventoryItem>();
Also: When you add an item to the collection you need to create a new item. Otherwise you are adding the same object, that will not work.
This is my reduced ViewModel to how I think you want it to work:
public class ViewModel : ViewModelBase
{
private ICommand _addItem;
public string InputName
{
get { return _inputName; }
set
{
if (_inputName != value)
{
_inputName = value;
NotifyPropertyChanged(nameof(InputName));
}
}
}
private string _inputName;
public ObservableCollection<InventoryItem> InventoryItems { get; } = new ObservableCollection<InventoryItem>();
public ICommand AddItem
{
get
{
if (_addItem == null)
{
_addItem = new RelayCommand(ParamArrayAttribute => this.Submit(), null);
}
return _addItem;
}
}
private void Submit()
{
if (InventoryItems.Any(p => p.Name == InputName))
{
InventoryItems.First(x => x.Name == InputName).ItemCount++;
}
else
{
InventoryItems.Add(new InventoryItem() { Name = InputName, ItemCount = 1 });
}
}
}
To complete the picture, I have added following XAML for test:
<TextBox Text="{Binding InputName}" MinWidth="100" Margin="5"/>
<Button Content="Add" Command="{Binding AddItem}" Margin="5"/>
I have a simple ListBox and a TextBox as under.I want to display the selectedvalue property of Listbox in the textbox,but my ViewModel's selected object is always null.
What am i missing here?
My XAML
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding CurrentRec.Name,Mode=OneWay}" />
<ListBox x:Name="AllMatching" Width="{Binding ElementName=TxtMail,Path=Width}" Height="100" Canvas.Top="54" Canvas.Left="36" DisplayMemberPath="Name" SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedValue="Name" SelectedValuePath="Name" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
My ViewModel:
public class VM_Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public List<DM_Data> AllData;
public DM_Data CurrentRec;
public VM_Data()
{
LoadData();
}
public int ID
{
get { return p_ID; }
set
{
if (p_ID != value)
{
RaisePropertyChangedEvent("ID");
p_ID = value;
}
}
}
public double SP
{
get { return p_SP; }
set
{
if (p_SP != value)
{
RaisePropertyChangedEvent("SP");
p_SP = value;
}
}
}
public double CP
{
get { return p_CP; }
set
{
if (p_CP != value)
{
RaisePropertyChangedEvent("CP");
p_CP = value;
}
}
}
public string Name
{
get { return p_Name; }
set
{
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
}
}
private void LoadData()
{
AllData = new List<DM_Data>();
string[] strNames = "Jatinder;Shashvat;shashikala;shamsher;shahid;justin;jatin;jolly;ajay;ahan;vijay;suresh;namita;nisha;negar;zenith;zan;zen;zutshi;harish;hercules;harman;ramesh;shashank;mandeep;aman;amandeep;amarjit;asim;akshay;amol;ritesh;ritivik;riz;samana;samaira;bhagwandass;bhagwan;bhawna;bhavna".Split(';');
for(int i=0;i<=strNames.GetUpperBound(0);i++)
{
DM_Data NewRec = new DM_Data();
NewRec.CP = new Random().Next(200, 400);
NewRec.SP = new Random().Next(1, 10);
NewRec.ID = i + 1;
NewRec.Name = strNames[i];
AllData.Add(NewRec);
}
AllData = AllData.OrderBy(item => item.Name).ToList();
}
private void RaisePropertyChangedEvent(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
}
My DataModel
public class DM_Data
{
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public int ID
{
get { return p_ID; }
set { p_ID = value; }
}
public double SP
{
get { return p_SP; }
set { p_SP = value; }
}
public double CP
{
get { return p_CP; }
set { p_CP = value; }
}
public string Name
{
get { return p_Name; }
set { p_Name = value; }
}
MainWindow.Xaml.cs
public partial class MainWindow : Window
{
VM_Data ViewModel;
public MainWindow()
{
InitializeComponent();
ViewModel = new VM_Data();
this.DataContext = ViewModel;
AllMatching.ItemsSource = ViewModel.AllData;
}
private void cmdtest_Click(object sender, RoutedEventArgs e)
{
DM_Data crec = ViewModel.CurrentRec;
}
}
CurrentRec must be a property that raises the PropertyChanged event:
private DM_Data _currentRec;
public DM_Data CurrentRec
{
get { return _currentRec; }
set { _currentRec = value; RaisePropertyChangedEvent("CurrentRec"); }
}
In the code you have posted, it is a field and you cannot bind to fields:
public DM_Data CurrentRec;
You can't bind to fields! CurrentRec must be a property. At now it is a field.
Why do you set ItemsSource in code-behind? Set it in XAML.
You should call RaisePropertyChangedEvent after you've changed backing field, not before.
It is not right pattern for events raising: if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(Property)); } You need to save event delegate to variable first or use ?.Invoke.
Don't create new instances of Random on every iteration of loop because you will get equal values. Create the only one outside of the loop and use it.
What you are doing is kind of MVVM, but not really.
Here is a quick fix anyway:
Please take a look at the Bindings.
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}" />
<ListBox x:Name="AllMatching"
Width="{Binding ElementName=TxtMail,Path=Width}"
Height="100"
Canvas.Top="54"
Canvas.Left="36"
DisplayMemberPath="Name"
SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
</StackPanel>
I think you get the idea at: Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}".
Aditional Information
First:
You fire to early dude. Please first assign the value and then say its changed.
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
Second:
Use a ObservableCollection<DM_Data> to let your ListBox know about changes.
Third:
Use the posibility of Binding
Remove AllMatching.ItemsSource = ViewModel.AllData; and go like
<ListBox x:Name="AllMatching"
ItemsSource="{Binding Path=AllData}"
...
/>
And after all of this - please man check out some tutorials. And also refactor your code from VM_Data to DataViewModel thank you sir.
This is an example code:
public class GridViewWindowViewModel : INotifyPropertyChanged
{
public GridViewWindowViewModel()
{
Tables = new ObservableCollection<string> { "Person", "Car" };
SainaAccessEntity = new SainaAccessEntity { TableName = "Person" };
}
private SainaAccessEntity _SainaAccessEntity;
public SainaAccessEntity SainaAccessEntity
{
get { return _SainaAccessEntity; }
set
{
if (_SainaAccessEntity != value)
{
_SainaAccessEntity = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<string> _Tables;
public ObservableCollection<string> Tables
{
get { return _Tables; }
set
{
if (_Tables != value)
{
_Tables = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
this is Model Class :
public class SainaAccessEntity : INotifyPropertyChanged
{
private string _TableName;
public string TableName
{
get { return _TableName; }
set
{
if (value != _TableName)
{
_TableName = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Custom Data Grid View:
public class MyDataGrid : DataGrid
{
public MyDataGrid()
{
var x = new ObservableCollection<Person> { new Person { Name = "Ali", Family = "Jalilvand" } };
}
public SainaAccessEntity SainaAccessEntity
{
get { return (SainaAccessEntity)GetValue(SainaAccessEntityProperty); }
set { SetValue(SainaAccessEntityProperty, value); }
}
public static readonly DependencyProperty SainaAccessEntityProperty =
DependencyProperty.Register("SainaAccessEntity", typeof(SainaAccessEntity), typeof(MyDataGrid), new PropertyMetadata(null, SainaAccessEntity_Changed));
private static void SainaAccessEntity_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var u = d as MyDataGrid;
if (u.SainaAccessEntity.TableName == "Person")
{
u.ItemsSource = new ObservableCollection<Person> { new Person { Name = "Ali", Family = "Bayat" } };
}
else
{
u.ItemsSource = new ObservableCollection<Car1> { new Car1 { Name = "BMW", Model = "518",Color="Red" } };
}
}
}
Car And Person Model:
public class Car1
{
public string Model { get; set; }
public string Name { get; set; }
public string Color { get; set; }
}
public class Person
{
public string Name { get; set; }
public string Family { get; set; }
}
Main Window XAML:
<Window x:Class="TestWpfApplication.GridViewWindow"
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:TestWpfApplication"
mc:Ignorable="d"
Title="GridViewWindow" Height="300" Width="300">
<Window.Resources>
<local:GridViewWindowViewModel x:Key="Vm"/>
</Window.Resources>
<Grid DataContext="{StaticResource Vm}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<local:MyDataGrid VerticalAlignment="Top" SainaAccessEntity="{Binding SainaAccessEntity, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox Margin="0,5.694,0,0" Grid.Row="1" ItemsSource="{Binding Tables, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SainaAccessEntity.TableName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></ComboBox>
</Grid>
</Window>
How to Update DataGrid ItemsSource when change its Property?
I have Custom DataGrid in WPF, it has a Property that define table name, and I want to dynamically change ItamsSource, when change this Property...
The SainaAccessEntity property of your custom DataGrid is bound to the SainaAccessEntity property of the view model but you never set this property when the selection in the ComboBox changes.
The Tables property of the view model should return an ObservableCollection<SainaAccessEntity> instead of an ObservableCollection<string>:
public class GridViewWindowViewModel : INotifyPropertyChanged
{
public GridViewWindowViewModel()
{
Tables = new ObservableCollection<SainaAccessEntity>
{
new SainaAccessEntity { TableName = "Person" },
new SainaAccessEntity { TableName = "Car" },
};
SainaAccessEntity = Tables[0];
}
private SainaAccessEntity _SainaAccessEntity;
public SainaAccessEntity SainaAccessEntity
{
get { return _SainaAccessEntity; }
set
{
if (_SainaAccessEntity != value)
{
_SainaAccessEntity = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<SainaAccessEntity> _Tables;
public ObservableCollection<SainaAccessEntity> Tables
{
get { return _Tables; }
set
{
if (_Tables != value)
{
_Tables = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You should then bind the SelectedItem property of the ComboBox to the SainaAccessEntity property of the view model and set the DisplayMemberPath property of the ComboBox to "TableName":
<ComboBox Margin="0,5.694,0,0" Grid.Row="1" ItemsSource="{Binding Tables, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SainaAccessEntity, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="TableName"></ComboBox>
With these changes the ItemsSource of the DataGrid should be changed as expected when you select an item in the ComboBox.
Here your property 'SainaAccessEntity' is not changing, only TableName is changing. For that you can try below code or you can change type from 'SainaAccessEntity' to string.
change 'SainaAccessEntity_Changed' method's code
var u = d as MyDataGrid;
if (u.SainaAccessEntity == null)
{
return;
}
// intial set value
if (u.SainaAccessEntity.TableName == "Person")
{
u.ItemsSource = new ObservableCollection<Person> { new Person { Name = "Ali", Family = "Bayat" } };
}
else
{
u.ItemsSource = new ObservableCollection<Car1> { new Car1 { Name = "BMW", Model = "518", Color = "Red" } };
}
// set value on change
var _notify = u.SainaAccessEntity as INotifyPropertyChanged;
if (_notify != null)
{
_notify.PropertyChanged += (sender, ev) =>
{
if (u.SainaAccessEntity.TableName == "Person")
{
u.ItemsSource = new ObservableCollection<Person> { new Person { Name = "Ali", Family = "Bayat" } };
}
else
{
u.ItemsSource = new ObservableCollection<Car1> { new Car1 { Name = "BMW", Model = "518", Color = "Red" } };
}
};
}
I am trying to write a simple application where I display a listview using databinding containing multiple objects that lists their properties and a check box. I let the user check all of the boxes they want removed then press a button that removes the selected elements.
public partial class MainWindow : Window
{
ObservableCollection<User> Users = new ObservableCollection<User>();
public MainWindow()
{
System.Console.WriteLine("main window");
Users.Add(new User() { Name = "John Doe", Age = 42, Height = "6ft", Checked = false});
Users.Add(new User() { Name = "Jane Doe", Age = 39, Height = "6ft", Checked = false });
Users.Add(new User() { Name = "Sammy Doe", Age = 7, Height = "5ft", Checked = false });
drawFolderView();
}
private void drawFolderView()
{
InitializeComponent();
lvUsers.ItemsSource = Users;
}
private void button_Click(object sender, RoutedEventArgs e)
{
if (Users.Count > 0)
{
List<User> itemsToRemove = new List<User>();
foreach (User person in Users)
{
if (person.Checked)
{
itemsToRemove.Add(person);
}
}
foreach (User person in itemsToRemove)
{
Users.Remove(person);
}
}
else
{
System.Console.WriteLine("nothing in list");
}
drawFolderView();
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Height { get; set; }
public bool Checked { get; set; }
public bool Equals(User other) {
if (Name.Equals(other.Name))
{
return true;
}
else
{
return false;
}
}
}
From reading the other questions I made the CheckBox_Checked and Unchecked methods, but I have no idea how to implement them.
Window x:Class="WpfApplication6.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:WpfApplication6"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListView Margin="10,10,10,98" Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
<GridViewColumn Header="Height" Width="150" DisplayMemberBinding="{Binding Height}" />
<GridViewColumn Width="60">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Margin="-4,0,-4,0" IsChecked="{Binding MyBoolProperty}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" DataContext="{Binding Checked}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<Button x:Name="button" Content="Delete" HorizontalAlignment="Left" Margin="353,243,0,0" VerticalAlignment="Top" Width="75" Click="button_Click" />
</Grid>
Is this a reasonable approach? Many of the concepts surrounding databinding in WPF still confuse me.
The Checkbox should be changed to this:
<CheckBox Margin="-4,0,-4,0" IsChecked="{Binding Checked}" />
The DataContext for the Checkbox is the User (like it is for the other columns). The IsChecked property of the Checkbox is bound to the Checked property on the User.
There is no need to call drawFolderView in the button_click method. The lvUsers.ItemsSource is already set to Users. Since Users is an ObservableCollection, it will raise events to tell WPF that items were removed/added and your listview will update itself automatically.
To work best with WPF, your User class should implement INotifyPropertyChanged. When the properties of User objects change, your bindings will get updated automatically. Right now, only the listview can change the User objects so you won't get any real benefit from INPC. But, if you added a button that looped through the Users and set User.Checked = true then you would need the INPC interface to tell WPF that the Checked property on the User objects changed so it could update the GUI.
Also, InitializeComponent() should be moved back to the top of the constructor. You only want that called when the window is being created.
Here is an updated version of your code with a few changes:
public partial class MainWindow : Window
{
ObservableCollection<User> Users = new ObservableCollection<User>();
public MainWindow()
{
InitializeComponent();
System.Console.WriteLine("main window");
Users.Add(new User() { Name = "John Doe", Age = 42, Height = "6ft", Checked = false });
Users.Add(new User() { Name = "Jane Doe", Age = 39, Height = "6ft", Checked = false });
Users.Add(new User() { Name = "Sammy Doe", Age = 7, Height = "5ft", Checked = false });
lvUsers.ItemsSource = Users;
}
private void button_Click(object sender, RoutedEventArgs e)
{
if (Users.Count > 0)
{
List<User> itemsToRemove = new List<User>();
foreach (User person in Users)
{
if (person.Checked)
{
itemsToRemove.Add(person);
}
}
foreach (User person in itemsToRemove)
{
Users.Remove(person);
}
}
else
{
System.Console.WriteLine("nothing in list");
}
}
}
public class User : System.ComponentModel.INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OPC(string propertyName) // OPC = OnPropertyChanged; a helper that raises the PropertyChanged event
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OPC("Name");
}
}
}
private int _age;
public int Age { get { return _age; } set { if (_age != value) { _age = value; OPC("Age"); } } }
private string _height;
public string Height { get { return _height; } set { if (_height != value) { _height = value; OPC("Height"); } } }
private bool _checked;
public bool Checked { get { return _checked; } set { if (_checked != value) { _checked = value; OPC("Checked"); } } }
public bool Equals(User other)
{
if (Name.Equals(other.Name))
{
return true;
}
else
{
return false;
}
}
}
I have a page with tabcontrol.
I am binding a list of locations a tabitem in the control.
The records are listed in a listview.
I am able to edit records by binding input controls to the listview.selecteditem.
My problem is when I want to add a new record. I want to minimise code behind.
ViewModel:
private ObservableCollection<LocationViewModel> _locations;
public ObservableCollection<LocationViewModel> Locations
{
get { return _locations; }
}
public LocationListViewModel()
{
_locations = new ObservableCollection<LocationViewModel>();
foreach (Service.Location l in service.GetLocationList().OrderBy(l => l.Building).ThenBy(l => l.Floor))
{
_locations.Add(new LocationViewModel
{
id = l.id,
Building = l.Building,
Floor = l.Floor,
RoomNo = l.RoomNo,
MapTitle = l.MapTitle,
MapExtension = l.MapExtension,
Map = l.Map,
DateCreated = l.DateCreated,
CreatedByID = l.CreatedByID,
CreatedByDesc = l.CreatedByDesc,
DateEdited = l.DateEdited,
EditedByID = l.EditedByID,
EditedByDesc = l.EditedByDesc
}
);
}
}
XML:
<TabItem x:Name="tabSettingsLocations" x:Uid="tabSettingsLocations"
Header="Locations"
DataContext="{StaticResource ResourceKey=LocationList}"> .....
Example of successful binding to listview for edits
<TextBox x:Name="txtSettingLocationBuildingEdit"
Margin="90,17,0,0" Style="{DynamicResource SettingsTextBoxStyle}"
Text="{Binding SelectedItem.Building, ElementName=lvwSettingsLocations,
Mode=TwoWay}" />
Example of unsuccessful binding for new record (uses different set of input controls)
<TextBox x:Name="txtSettingLocationBuildingAdd"
Margin="90,17,0,0" Style="{DynamicResource SettingsTextBoxStyle}"
Text="{Binding Building, ElementName=lvwSettingsLocations,
Mode=OneWayToSource}"/>
I also tried to bind the child tab item to the same data source
<TabItem x:Name="tbSettingsLocationsAdd" x:Uid="tbSettingsLocationsAdd"
Header="Add New"
DataContext="{StaticResource ResourceKey=LocationList}">
<TextBox x:Name="txtSettingLocationBuildingAdd"
Margin="90,17,0,0" Style="{DynamicResource SettingsTextBoxStyle}"
Text="{Binding Building}"/>
To no avail.
I also tried creating a new child dataview but I want it all to be bound together so that the interface updates whatever I do add or edit.
Anyone help?
Okay so I nailed this in the end. Just wanted to share ... thanks to Silvermind for a good tip on best practice.
Command:
class Location_Add : ICommand
{
private ObservableCollection<LocationViewModel> _llvm;
public ObservableCollection<LocationViewModel> llvm
{
get { return _llvm; }
}
public Location_Add(ObservableCollection<LocationViewModel> passedllvm)
{
_llvm = passedllvm;
}
public bool CanExecute(object parameter)
{
LocationViewModel lvw = parameter as LocationViewModel;
return lvw != null;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
LocationViewModel lvm = parameter as LocationViewModel;
llvm.Add(lvm);
AddLocation(lvm);
}
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
public void AddLocation(LocationViewModel lvm)
{
try
{
Service.SchoolMonitorServiceClient service = new Service.SchoolMonitorServiceClient();
Service.Location loc = new Service.Location();
loc.Building = lvm.Building.Trim();
loc.Floor = lvm.Floor.Trim();
loc.RoomNo = lvm.RoomNo.Trim();
loc.MapTitle = lvm.MapTitle;
loc.MapExtension = lvm.MapTitle.Substring(lvm.MapTitle.IndexOf("."));
loc.Map = lvm.Map;
loc.DateCreated = DateTime.Now;
loc.CreatedByID = (Int32)Application.Current.Resources["UserID"];
loc.DateEdited = lvm.DateEdited;
service.AddLocation(loc);
MessageBox.Show("Your new Location was entered successfully", "Success", MessageBoxButton.OK);
}
catch (Exception e)
{
.....
}
}
}
ViewModel:
class LocationListViewModel
{
Service.SchoolMonitorServiceClient service = new Service.SchoolMonitorServiceClient();
#region Members
private ObservableCollection<LocationViewModel> _locations;
private Location_Add _AddCommand;
#endregion
#region Properties
public ObservableCollection<LocationViewModel> Locations
{
get { return _locations; }
}
#endregion
public LocationListViewModel()
{
_locations = new ObservableCollection<LocationViewModel>();
foreach (Service.Location l
in service.GetLocationList()
.OrderBy(l => l.Building).ThenBy(l => l.Floor))
{
_locations.Add(new LocationViewModel
{
id = l.id,
Building = l.Building,
Floor = l.Floor,
RoomNo = l.RoomNo,
MapTitle = l.MapTitle,
MapExtension = l.MapExtension,
Map = l.Map,
DateCreated = l.DateCreated,
CreatedByID = l.CreatedByID,
CreatedByDesc = l.CreatedByDesc,
DateEdited = l.DateEdited,
EditedByID = l.EditedByID,
EditedByDesc = l.EditedByDesc
}
);
}
_AddCommand = new Location_Add(_locations);
}
public ICommand AddCommand
{
get
{
return _AddCommand;
}
}
}
XML:
xmlns:local="clr-namespace:SchoolMonitor_WPF.ViewModels"
<Page.Resources>
<local:LocationListViewModel x:Key="LocationList" />
<local:LocationViewModel x:Key="NewLocation" />
</Page.Resources>
<TextBox x:Name="txtSettingLocationBuildingAdd" x:Uid="txtSettingLocationBuildingAdd"
Margin="90,17,0,0" Style="{DynamicResource SettingsTextBoxStyle}"
DataContext="{StaticResource ResourceKey=NewLocation}"
Text="{Binding Path=Building}"/>
<Button x:Name="btnSettingsLocationSaveAdd" Content="Submit" Margin="0,80,10,0"
VerticalAlignment="Top" Style="{DynamicResource ButtonStyle}"
HorizontalAlignment="Right" Width="75"
DataContext="{StaticResource ResourceKey=LocationList}"
CommandParameter="{StaticResource ResourceKey=NewLocation}"
Command="{Binding AddCommand}">
Hope this helps someone.