WPF - Observable Collection new record from xml binding - wpf

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.

Related

wpf ListItem SelectedValue Object is always null

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.

Tabs not showing when adding them through method

I have a datagrid. I want to add Tab when row double click event is rised. When AddTab method is called Tab is added to ObservableCollection, but it doesn't show up on TabControl. Why it doesn't show up? Because there is a wrong DataContext when method called?
<DataGrid helpers:RowDoubleClickHandler.MethodName="AddTab" AutoGenerateColumns="False"
IsSynchronizedWithCurrentItem="True" CanUserResizeRows="True" x:Name="dataGrid1"
ItemsSource="{Binding DataGridEntries3}" HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" IsReadOnly="True">
AddTab method in ViewModel
public void AddTab()
{
Tabs.Add(new TabEntry
{
Description = "Tab3",
DataGridEntries = new ObservableCollection<DataGridEntry>()
{
new DataGridEntry()
{
}
}
});
XAML with TabControl
<Window x:Class="ProjectZero.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ProjectZero"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<vm:MainViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid>
<ToolBar Height="40" VerticalAlignment="Top">
<Menu>
<MenuItem Header="Menu" Margin="6" Foreground="White" FontSize="14" FontFamily="Times New Roman">
<MenuItem Header="Add Invoice" Command="{Binding AddInvoice}"/>
<MenuItem Header="Invoices List" Command="{Binding AddInvoiceList}" FontFamily="Tahoma" />
</MenuItem>
<Menu.Background>
<SolidColorBrush />
</Menu.Background>
</Menu>
<ToolBar.Background>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="#FF173ADE" Offset="0.431" />
<GradientStop Color="#FF0B1D6F" Offset="0.646" />
</LinearGradientBrush>
</ToolBar.Background>
</ToolBar>
<TabControl x:Name="tabControl1" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Tabs}" ItemTemplate="{DynamicResource DataTemplateType1}" TabStripPlacement="Top" HorizontalAlignment="Stretch" Margin="10,46,0,0" VerticalAlignment="Stretch" Width="Auto">
</TabControl>
ViewModel for Tabs
public class MainViewModel : BaseViewModel
{
public RelayCommand RelayCommand { get; set; }
public MainViewModel()
{
this.RelayCommand = new RelayCommand(this);
Tabs.CollectionChanged += (o, e) =>
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (BaseViewModel item in e.NewItems)
if (item.MyType == "TabEntry")
DataGridEntries3.Add(item as TabEntry);
}
};
var t = (from i in Tabs where i.MyType == "TabEntry" select (TabEntry)i);
DataGridEntries3 = new ObservableCollection<TabEntry>(t);
}
private ObservableCollection<BaseViewModel> _tabs;
public ObservableCollection<BaseViewModel> Tabs
{
get { return _tabs != null ? _tabs : _tabs = new ObservableCollection<BaseViewModel>(); }
set { _tabs = value; OnPropertyChanged("Tabs"); }
}
BaseViewModel _SelectedItem;
public BaseViewModel SelectedItem
{
get { return _SelectedItem; }
set { _SelectedItem = value; OnPropertyChanged("SelectedItem"); }
}
private ObservableCollection<TabEntry> _DataGridEntries3;
public ObservableCollection<TabEntry> DataGridEntries3
{
get { return _DataGridEntries3 != null ? _DataGridEntries3 : _DataGridEntries3 = new ObservableCollection<TabEntry>(); }
set { _DataGridEntries3 = value; OnPropertyChanged("DataGridEntries3"); }
}
ICommand _AddInvoice = null;
ICommand _AddInvoiceList = null;
public ICommand AddInvoice
{
get
{
return _AddInvoice != null ? _AddInvoice : _AddInvoice.SetCommand(param =>
{
Tabs.Add(new TabEntry
{
Description = "Tab3",
DataGridEntries = new ObservableCollection<DataGridEntry>()
{
new DataGridEntry()
{
}
}
});
});
}
}
public ICommand AddInvoiceList
{
get
{
return _AddInvoiceList != null ? _AddInvoiceList : _AddInvoiceList.SetCommand(param =>
{
var tab_dc = Tabs.FirstOrDefault(it => it.GetType() == typeof(MainViewModel));
if (tab_dc != null)
{
Tabs.Add(tab_dc);
}
else
{
var new_tab = new MainViewModel();
Tabs.Add(new_tab);
}
});
}
}
}
Also here is RowDoubleClick Event Handler
public sealed class RowDoubleClickHandler : FrameworkElement
{
public RowDoubleClickHandler(DataGrid dataGrid)
{
MouseButtonEventHandler handler = (sender, args) =>
{
var row = sender as DataGridRow;
if (row != null && row.IsSelected)
{
var methodName = GetMethodName(dataGrid);
var dataContextType = dataGrid.DataContext.GetType();
var method = dataContextType.GetMethod(methodName);
if (method == null)
{
throw new MissingMethodException(methodName);
}
method.Invoke(dataGrid.DataContext, null);
}
};
dataGrid.LoadingRow += (s, e) =>
{
e.Row.MouseDoubleClick += handler;
};
dataGrid.UnloadingRow += (s, e) =>
{
e.Row.MouseDoubleClick -= handler;
};
}
public static string GetMethodName(DataGrid dataGrid)
{
return (string)dataGrid.GetValue(MethodNameProperty);
}
public static void SetMethodName(DataGrid dataGrid, string value)
{
dataGrid.SetValue(MethodNameProperty, value);
}
public static readonly DependencyProperty MethodNameProperty = DependencyProperty.RegisterAttached(
"MethodName",
typeof(string),
typeof(RowDoubleClickHandler),
new PropertyMetadata((o, e) =>
{
var dataGrid = o as DataGrid;
if (dataGrid != null)
{
new RowDoubleClickHandler(dataGrid);
}
}));
}
A few tips, since you asked in your comment:
This:
private ObservableCollection<BaseViewModel> _tabs;
public ObservableCollection<BaseViewModel> Tabs
{
get { return _tabs != null ? _tabs : _tabs = new ObservableCollection<BaseViewModel>(); }
set { _tabs = value; OnPropertyChanged("Tabs"); }
}
Could be rewritten a lot cleaner as:
public ObservableCollection<BaseViewModel> Tabs { get; private set; }
With this added to your constructor:
Tabs = new ObservableCollection<BaseViewModel>();
This is a consistent theme in your code. Basically, don't make modifications in your property getters/setters unless you really really need to. And in your case, all it is doing is complicating things.
It is especially confusing when you have a getter that then will add a tab to your collection of tabs when first invoked. That is very difficult to follow programmatically. Just do your initialization in your constructor like everyone else does :)

Combobox data binding with item templates

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.

databinding combobox in Dataform Silverlight using MVVM in Update

I have Master/Detail – datagrid/dataform and after select item it shows in dataform for update, but I have a problem with databinding or populating combox with departments and set SelectedEmployee.departmentid as selectedvalue.
Here 2 questions now:
1. In EmployeeViewModel this code just doesn’t work and the question why?
private ObservableCollection<department> _departments;
public ObservableCollection<department> Departments
{
get { return _departments; }
set
{
_departments = value;
RaisePropertyChanged("Departments");
}
}
But this code works fine
private ObservableCollection<department> _departments;
public ObservableCollection<department> Departments
{
get {
if (_departments == null)
{
_departments = new ObservableCollection<department> {
new department()
{ id = 1, departmentname = "Technical " + 1, },
new department()
{ id = 2, departmentname = "Technical " + 2, },
new department()
{ id = 3, departmentname = "Technical " + 3, }
};
}
return _departments;
}
set
{
_departments = value;
RaisePropertyChanged("Departments");
}
}
2. Behavior of Combobox inside and outside DataForm is different. Outside it works, inside it doesn’t. I think here need to use Source in ItemsSource, but I don’t know how. So there is another question is how to fix it?
employeeView.xaml
<navigation:Page xmlns:local="clr-namespace:departmentTechManager"
x:Class="departmentTechManager.Views.employeeView"
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"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="820" d:DesignHeight="780"
Title="employees"
Style="{StaticResource PageStyle}"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mvvmlightcmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL4"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:dataformtoolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
DataContext="{Binding employeeStatic, Source={StaticResource Locator}}">
<data:DataGrid Grid.Row="0" x:Name="dgEmployees" CanUserSortColumns="true"
IsReadOnly="true" AutoGenerateColumns="true"
ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee, Mode=TwoWay}" Margin="0,36,-123,0"></data:DataGrid>
<dataformtoolkit:DataForm x:Name="dfDetails"
CurrentItem="{Binding SelectedEmployee}" AutoGenerateFields="False"
CommitButtonContent="Save" CommandButtonsVisibility="Edit, Commit, Cancel">
<dataformtoolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataformtoolkit:DataField Label="name">
<TextBox Text="{Binding name, Mode=TwoWay}" /></dataformtoolkit:DataField>
<dataformtoolkit:DataField Label="departments">
<ComboBox ItemsSource="{Binding Departments}"
DisplayMemberPath="departmentname"
SelectedValuePath="id"
SelectedValue="{Binding Path=SelectedEmployee.departmentid, Mode=TwoWay}" />
</dataformtoolkit:DataField>
</StackPanel>
</DataTemplate>
</dataformtoolkit:DataForm.EditTemplate>
<i:Interaction.Triggers><i:EventTrigger EventName="EditEnded">
<mvvmlightcmd:EventToCommand Command="{Binding SaveEmployeesCommand}"/>
</i:EventTrigger></i:Interaction.Triggers>
</dataformtoolkit:DataForm>
In ViewModelLocator.cs:
public ViewModelLocator()
{
_sp = ServiceProviderBase.Instance;
Createdepartment();
Createemployee();
}
#region EmployeeViewModel
private static EmployeeViewModel _employee;
public static EmployeeViewModel employeeStatic
{
get
{
if (_employee == null)
{
Createemployee();
}
return _employee;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public EmployeeViewModel employee
{
get
{
return employeeStatic;
}
}
public static void Clearemployee()
{
//do it later
//_employee.Cleanup();
_employee = null;
}
public static void Createemployee()
{
if (_employee == null)
{
_employee = new EmployeeViewModel(_sp.PageConductor, _sp.EmployeeDataService);
}
}
#endregion
#region DepartmentViewModel
private static DepartmentViewModel _department;
public static DepartmentViewModel departmentStatic
{
get
{
if (_department == null)
{
Createdepartment();
}
return _department;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public DepartmentViewModel department
{
get
{
return departmentStatic;
}
}
public static void Cleardepartment()
{
//do it later
//_department.Cleanup();
_department = null;
}
public static void Createdepartment()
{
if (_department == null)
{
_department = new DepartmentViewModel(_sp.PageConductor, _sp.DepartmentDataService);
}
}
#endregion
Can somebody help me?
combox is just empty. but now i can populate it with Departments like so:
departmentTechManagerDomainService.metadata.cs
[MetadataTypeAttribute(typeof(employee.employeeMetadata))]
public partial class employee
{
[Include]
public department department { get; set; }
public Nullable<int> departmentid { get; set; }
public string name { get; set; }
}
departmentTechManagerDomainService.cs
public IQueryable<employee> GetEmployees()
{return this.ObjectContext.employees.Include("department").OrderBy(e=>e.name);}
here is ViewModel code:
private ObservableCollection<department> _departments;
public ObservableCollection<department> Departments
{
get { return _departments; }
set
{
_departments = value;
RaisePropertyChanged("Departments");
}
}
private department _selectedDepartment;
public department SelectedDepartment
{
get { return _selectedDepartment; }
set
{
_selectedDepartment = value;
RaisePropertyChanged("SelectedDepartment");
}
}
private void InitializeModels()
{
Employees = new ObservableCollection<employee>();
SelectedEmployee = new employee();
NewEmployee = new employee();
//new
Departments = new ObservableCollection<department>();
SelectedDepartment = new department();
}
private void GetEmployeesCallback(IEnumerable<employee> employees)
{
if (employees != null)
{
foreach (var employee in employees)
{
Employees.Add(employee);
//new
if (!Departments.Contains(employee.department))
Departments.Add(employee.department);
}
if (Employees.Count > 0)
{
SelectedEmployee = Employees[0];
}
}
}
I make Departments distinct, but here is only departments those already have been selected, but here aren't those that haven't been selected yet, and still combobox is not populated with departments in DataForm. ?!
2nd question - it looks like combobox inside and outside of dataform receives different DataContext and hence trying to locate Departments properties on different sources. It is not clear how to fix it since you haven't shown most of your ViewModels.
Pay attention to the VS output window, it usually provides very detailed info regarding binding errors and I'm assuming there are binding errors in your case.
Try modifying your department related bindings in following way:
<ComboBox ItemsSource="{Binding DataContext.Departments, RelativeSoruce={RelativeSource AncestorType={x:Type localViews:employeeView}}}" />
Where localViews should be an xml-namespace for departmentTechManager.Views. Try the same trick for SelectedItem binding.
I've got the solution for this question. here it is.
In Edit Template, Source must need to be mention with ViewModel Name
<ComboBox Grid.Row="2" Grid.Column="1"
ItemsSource="{Binding Path=Accounts, Source={StaticResource MyAccountViewModel}, Mode=TwoWay}" />

WPF Master-Details view with Listbox and Combobox with Binding

I've been looking around for an answer to my question for a few days now, but am not able to find a solution.
The problem is that the combobox updates the Test object in User class with the the previous selected Users'.
i.e. you select user2 and user2 has test2, you then select user5 that has test5. Now if you select user2 again, it will show that it has test5.
Here is some code. I have two classes Users and Tests. And two ObservableCollections for each of those. This is how I've got them setup:
public class User
{
public string Name { get; set; }
public int test { get; set; }
public test userTest { get; set; }
}
public class test
{
public int ID { get; set; }
public String Name { get; set; }
}
public class ListOfTests:ObservableCollection<test>
{
public ListOfTests()
{
for (int i = 0; i < 4; i++)
{
test newTest = new test();
newTest.ID = i;
newTest.Name = "Test " + i;
Add(newTest);
}
}
}
public class ListOfUsers: ObservableCollection<User>
{
public ListOfUsers()
{
ListOfTests testlist = new ListOfTests();
for (int i = 0; i < 10; i++)
{
User newUser = new User();
newUser.Name = "User " + i;
newUser.ID = i;
newUser.userTest = testlist[i];
Add(newUser);
}
}
}
And the XAML is:
<Window x:Class="ComboboxTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ComboboxTest"
Title="Window1" Height="300" Width="300">
<StackPanel x:Name="SP1">
<StackPanel.Resources>
<local:ListOfTests x:Key="ListOfTests" />
</StackPanel.Resources>
<ListBox ItemsSource="{Binding}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True"/>
<TextBox Text="{Binding Path=Name}" Foreground="Black" />
<TextBox Text="{Binding Path=userTest}" />
<ComboBox SelectedItem="{Binding Path=userTest}"
SelectedValue="{Binding Path=userTest.ID}"
ItemsSource="{Binding Source={StaticResource ListOfTests}}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
Foreground="Black" />
</StackPanel>
Now if I change the Binding on the SelectedItem to "{Binding Path=userTest, Mode=OneWay}" then it works, but i can not change the it manually.
Here is a kicker thought... If I target .Net 4.0 (VS2010) then it works fine...
Can anyone please help me find a solution to this?
If I'm understanding your question, it sounds like that WPF isn't being notified when the value of a property changes. You can get around this by implementing the INotifyPropertyChanged interface. For example, the User class would look something like this:
public class User : INotifyPropertyChanged
{
private string name = string.Empty;
public string Name
{
get { return this.name; }
set
{
this.name = value;
this.OnPropertyChanged("Name");
}
}
private int test = 0;
public int Test
{
get { return this.test; }
set
{
this.test = value;
this.OnPropertyChanged("Test");
}
}
private test userTest = null;
public test UserTest
{
get { return this.userTest; }
set
{
this.userTest = value;
this.OnPropertyChanged("UserTest");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler eh = this.PropertyChangd;
if(null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
You should probably do the same for your test class, as well.
WPF will watch for when the PropertyChanged event is fired, and updates any affected bindings as needed. This should cause the selected item in the ComboBox to change back to the test for user2.
Update: OK, I think I got this working. I think that you're missing part of the code in what you posted (like what the DataContext for the Window is), but here's what I got working:
I created a class called ViewModel, which is set to the DataContext of the main Window. Here's its code:
class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
for(int i = 0; i < 4; i++)
{
this.tests.Add(new Test()
{
ID = i,
Name = "Test " + i.ToString(),
});
}
for(int i = 0; i < 4; i++)
{
this.users.Add(new User()
{
Name = "User " + i.ToString(),
ID = i,
UserTest = this.tests[i],
});
}
}
private ObservableCollection<User> users = new ObservableCollection<User>();
public IEnumerable<User> Users
{
get { return this.users; }
}
private ObservableCollection<Test> tests = new ObservableCollection<Test>();
public IEnumerable<Test> Tests
{
get { return this.tests; }
}
private User currentUser = null;
public User CurrentUser
{
get { return this.currentUser; }
set
{
this.currentUser = value;
this.OnPropertyChanged("CurrentUser");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
var eh = this.PropertyChanged;
if(null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
I moved the creation of the two lists to code. One thing I noticed in your sample is that one instance of ListOfTests was used as the ItemsSource of the ComboBox, while another instance was used to build ListOfUsers. I'm not sure if that was part of the problem or not, but it is better to just have one list of tests.
The XAML for the main Window is the following:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ListBox ItemsSource="{Binding Path=Users}"
SelectedItem="{Binding Path=CurrentUser}"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True">
</ListBox>
<TextBox Text="{Binding Path=CurrentUser.Name}" />
<TextBox Text="{Binding Path=CurrentUser.UserTest.Name}" />
<ComboBox ItemsSource="{Binding Path=Tests}"
SelectedItem="{Binding Path=CurrentUser.UserTest}"
DisplayMemberPath="Name" />
</StackPanel>
</Window>
The key to getting things working is the CurrentUser property. It is bound to ListBox.SelectedItem, and ComboBox.SelectedItem is bound to CurrentUser.UserTest. This will change the selection in the ComboBox to represent the test of the user selected in the ListBox.
I got this all working using Visual Studio 2008 SP1, so hopefully it will work for you as well. If you have any problems getting this working, let me know and I'll see what I can do.
Andy,
Here is a more readable extract from the code I have now.
public class User : INotifyPropertyChanged
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
private int iD;
public int ID
{
get
{
return iD;
}
set
{
iD = value;
OnPropertyChanged("ID");
}
}
private test userTest;
public test UserTest
{
get
{
return userTest;
}
set
{
userTest = value;
OnPropertyChanged("UserTest");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler eh = this.PropertyChanged;
if (null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
Looks better than in the comments.
Regards
Corne

Resources