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 :)
Related
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.
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.
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.
I want to pass selected items of listview checkbox in view model, later I will use further process to store in database.
Code in the FormWeek.xaml as
<Window.DataContext>
<Binding Source="{StaticResource Locator}" Path="TaskExecDefModel"></Binding>
</Window.DataContext>
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="ItemDataTemplate">
<CheckBox
x:Name="checkbox"
Content="{Binding}" Command="{Binding CheckBoxCommand}" CommandParameter="{Binding ElementName=checkedListView, Path=SelectedItems}"
IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Path=IsSelected}" />
</DataTemplate>
</ResourceDictionary>
<StackPanel Grid.Column="1" Grid.Row="3" Margin="5">
<CheckBox x:Name="selectAll" Content="Select all" Click="OnSelectAllChanged"/>
<ListView x:Name="checkedListView" SelectionMode="Multiple" ItemsSource="{Binding CollectionOfDays}" DataContext="{Binding}"
ItemTemplate="{StaticResource ItemDataTemplate}" SelectedValue="WorkingDay"
CheckBox.Unchecked="OnUncheckItem" SelectionChanged="SelectDays" SelectedItem="{Binding SelectedItems}">
</ListView>
</StackPanel>
Code in FormWeek.xaml.cs
private void SelectDays(object sender, SelectionChangedEventArgs e)
{
(this.DataContext as TaskExecDefinitionViewModel).OnCheckBoxCommand(checkedListView.SelectedItems,true);
}
My View Model TaskWeek.cs as follows
//Declaration
private RelayCommand<object> _checkBoxCommand;
public ObservableCollection<string> CollectionOfDays { get; set; }
public ObservableCollection<string> SelectedItems { get; set; }
public RelayCommand<object> CheckBoxCommand
{
get
{
if (_checkBoxCommand == null)
{
_checkBoxCommand = new RelayCommand<object>((args) => OnCheckBoxCommand(args,true));
// _checkBoxCommand = new RelayCommand(() => OnCheckBoxCommand(object args));
}
return _checkBoxCommand;
}
}
//Constructor
CollectionOfDays = new ObservableCollection<string>();
//Method
private void GetWeekDays()
{
try
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
CollectionOfDays.Add("Saturday");
CollectionOfDays.Add("Sunday");
CollectionOfDays.Add("Monday");
CollectionOfDays.Add("Tuesday");
CollectionOfDays.Add("Wednesday");
CollectionOfDays.Add("Thursday");
CollectionOfDays.Add("Friday");
}));
}
catch(Exception Ex)
{
MessageBox.Show(Ex.Message, "TaskWeek:GetWeekDays");
}
}
public void OnCheckBoxCommand(object obj, bool _direction)
{
try
{
if (SelectedItems == null)
SelectedItems = new ObservableCollection<string>();
if (obj != null)
{
SelectedItems.Clear();
StringBuilder items = new StringBuilder();
if (_direction)
{
foreach (string item in CollectionOfDays)
{
items.AppendFormat(item + ",");
}
}
MessageBox.Show(items.ToString());
}
else
SelectedItems.Clear();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "TaskDefinition:OnCheckBoxCommand");
}
}
And below is button click commond to save the data.
<Button Grid.Column="2" Grid.Row="2" Content="Save" HorizontalAlignment="Center" VerticalAlignment="Bottom"
Command="{Binding InsertExecDefCommand}" Margin="5" >
Now my requirement is to pass selected listview items to view model through command object. I had done this using following code in FormWeek.xam.cs through SelectionChanged event as
private void OnSelectedItems(object sender, RoutedEventArgs e)
{
StringBuilder items = new StringBuilder();
foreach (string item in checkedListView.SelectedItems)
{
items.AppendFormat(item + ",");
}
string AllDays= items.ToString();
}
But please let me know how to achieve this logic through MVVM. How to get selecteditems in my view model TaskWeek.cs
After R&D and google i had done chages through RelayCommand. In OnCheckBoxCommand method foreach statement is wrong it is passing all days. I want to pass only selected listview item. Please suggest me what is wrong in OnCheckBoxCommand method.
Here are my findings;
use this code in code behind of FormWeek.xaml:
private void SelectDays(object sender, SelectionChangedEventArgs e)
{
(this.DataContext as TaskExecDefinitionViewModel).OnCheckBoxCommand(checkedListView.SelectedItems,true);
}
And in 'OnCheckBoxCommand': -
public void OnCheckBoxCommand(object obj, bool _direction)
{
try
{
if (SelectedItems == null) SelectedItems = new ObservableCollection<string>();
if (obj != null)
{
SelectedItems.Clear();
var _list = ((IList)obj).Cast<string>().ToList();
if (_direction)
{
_list.ForEach(item => SelectedItems.Add(item));
}
}
else
SelectedItems.Clear();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "TaskDefinition:OnCheckBoxCommand");
}
}
Have a nice day man.....keep going.
Hi try something like this
<StackPanel Grid.Column="1" Grid.Row="3" Margin="5">
<CheckBox x:Name="selectAll" Content="Select all" Command="{Binding CheckBoxCommand}" CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Mode=Self}}"/>
<ListView x:Name="checkedListView" SelectionMode="Extended" ItemsSource="{Binding CollectionOfDays}" SelectedItem="{Binding SelectedItems}"/>
</StackPanel>
public class MainViewModel
{
public MainViewModel()
{
CollectionOfDays = new ObservableCollection<string>();
SelectedItems = new ObservableCollection<string>();
CollectionOfDays.Add("Saturday");
CollectionOfDays.Add("Sunday");
CollectionOfDays.Add("Monday");
CollectionOfDays.Add("Tuesday");
CollectionOfDays.Add("Wednesday");
CollectionOfDays.Add("Thursday");
CollectionOfDays.Add("Friday");
}
private CommandHandler _checkBoxCommand;
public CommandHandler CheckBoxCommand
{
get
{
return _checkBoxCommand ?? (_checkBoxCommand=new CommandHandler((param)=>OnCheckBoxCommand(param)));
}
}
public ObservableCollection<string> CollectionOfDays { get; set; }
public ObservableCollection<string> SelectedItems {get;set;}
private void OnCheckBoxCommand(object obj)
{
if (obj is bool)
{
if (SelectedItems == null)
SelectedItems = new ObservableCollection<string>();
if ((bool)obj)
{
SelectedItems.Clear();
foreach (var item in CollectionOfDays)
{
SelectedItems.Add(item);
}
}
else
SelectedItems.Clear();
}
}
}
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
I hope this will give you an idea.
I tried to find a solution with asynchronous DataProvider, but everywhere is bound to a static class, but I need a dynamic binding to different objects.
I tried to use the this solution but my UI still freezes. Anyone can explain why I get this behavior and how do I make UI active.
Application XAML file:
<Window x:Class="WpfApplication_async.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication_async"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:RecordValueDataTemplateSelector x:Key="myDataTemplateSelector"/>
<DataTemplate x:Key="ComboBoxTemplate">
<ComboBox Width="{Binding Path=ColumnDef.ColumnWidth,Mode=OneTime}"
ItemsSource="{Binding Path=viewEntityList,Mode=TwoWay}"
SelectedIndex="{Binding Path=Index, Mode=TwoWay}">
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="TextBoxTemplate">
<TextBox Text="{Binding Path=Text,Mode=TwoWay}" Width="{Binding Path=ColumnDef.ColumnWidth,Mode=OneTime}"/>
</DataTemplate>
<DataTemplate x:Key="HeaderTextBlockTemplate">
<TextBlock Width="{Binding Path=ColumnWidth,Mode=OneTime}" Text="{Binding Path=ColumnName,Mode=TwoWay}" ToolTip="{Binding Path=ColumnName}" />
</DataTemplate>
<DataTemplate x:Key="ListBoxTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Key, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" Width="80"/>
<ListBox ItemsSource="{Binding Path=Items}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.CanContentScroll="False" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Visible">
<DockPanel>
<ListBox Name="headerListBox" MinHeight="20" DockPanel.Dock="Top"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.CanContentScroll="False"
ItemTemplate="{StaticResource HeaderTextBlockTemplate}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<ScrollViewer Name="listScrollViewer" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible">
<ListView Name="listView" SelectionMode="Extended"
VirtualizingStackPanel.IsVirtualizing="True"
ScrollViewer.IsDeferredScrollingEnabled="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ListBoxTemplate}" >
</ListView>
</ScrollViewer>
</DockPanel>
</ScrollViewer>
<Button Grid.Row="1" Content="Button" Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window>
My dataProvider classes:
using System;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using System.Windows.Data;
using System.Windows;
using DataVirtualization;
using System.Threading;
//using System.Threading;
namespace WpfApplication_async
{
public enum MySubKeyValueType { StringValue=0, ListValue=1 }
public class MySection : IItemsProvider<MyRecord>
{
/// <summary>
/// Fetches the total number of items available.
/// </summary>
/// <returns></returns>
public int FetchCount()
{
//Thread.Sleep(1000);
return Records.Count;
}
/// <summary>
/// Fetches a range of items.
/// </summary>
/// <param name="startIndex">The start index.</param>
/// <param name="count">The number of items to fetch.</param>
/// <returns></returns>
public IList<MyRecord> FetchRange(int startIndex, int count)
{
//Thread.Sleep(1000);
if (startIndex > Records.Count) startIndex = Records.Count;
if (startIndex + count > Records.Count) count = Records.Count - startIndex;
return Records.ToList().GetRange(startIndex, count).ToList();
}
public MySection()
{
Records = new ObservableCollection<MyRecord>();
}
public ObservableCollection<MyRecord> Records { get; set;}
}
public class MyRecord : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
public MyRecord()
{
Items = new ObservableCollection<MySubKeyValue>();
}
private string key;
public string Key {
get {
return key;
}
set {
//if (Key!=null && this.Parent is MySection && (this.Parent as MySection).SectionDefinition.IsNumberedKeys)
// return;
key = value;
OnPropertyChanged("Key");
}
}
ObservableCollection<MySubKeyValue> items = new ObservableCollection<MySubKeyValue>();
public ObservableCollection<MySubKeyValue> Items
{
get {
return items;
}
set {
items = value;
OnPropertyChanged("NumberedColumnText");
}
}
}
public class MySubKeyValue : DependencyObject, INotifyPropertyChanged
{
private ColumnDefinition columnDef = null;
public ColumnDefinition ColumnDef
{
get
{
if (columnDef == null)
return columnDef = new ColumnDefinition();
return columnDef;
}
set { columnDef = value; }
}
public MySubKeyValue(string str = null)
{
Text = str;
ValueType = MySubKeyValueType.StringValue;
IsValidData = true;
ErrorMessage = "error";
}
private string text;
public MySubKeyValueType ValueType { get; set; }
public string Text
{
get
{
if (text == null) return String.Empty;
return text;
}
set
{
if (text != value)
{
text = value;
OnPropertyChanged("Text");
}
}
}
public bool isValidData = true;
public bool IsValidData
{
get { return isValidData; }
set
{
if (isValidData != value)
{
isValidData = value;
OnPropertyChanged("IsValidData");
}
}
}
public string ErrorMessage { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
public class StringValue : MySubKeyValue
{
public StringValue(string str = null)
{
ValueType = MySubKeyValueType.StringValue;
Text = str;
}
}
public class ListValue : MySubKeyValue
{
private int index;
public int Index
{
get { return index; }
set
{
index = value;
if (index > -1 && index < valueEntityList.Count)
{
base.Text = valueEntityList[index];
IsValidData = true;
}
else
IsValidData = false;
OnPropertyChanged("Index");
}
}
public List<string> valueEntityList { get; set; }
public List<string> viewEntityList { get; set; }
public ListValue(string str, ListValue l)
{
ValueType = MySubKeyValueType.ListValue;
valueEntityList = l.valueEntityList;
viewEntityList = l.viewEntityList;
base.Text = str;
Index = valueEntityList.FindIndex(v => v == str);
}
public ListValue(List<string> _vals = null, List<string> _views = null, string str = "")
{
Index = -1;
ValueType = MySubKeyValueType.ListValue;
valueEntityList = new List<string>();
viewEntityList = new List<string>();
if (_vals != null)
if (_views != null && _views.Count == _vals.Count)
{
valueEntityList.AddRange(_vals);
viewEntityList.AddRange(_views);
}
else
{
valueEntityList.AddRange(_vals);
viewEntityList.AddRange(_vals);
}
base.Text = str;
Index = valueEntityList.FindIndex(v => v == str);
}
}
public class ColumnDefinition : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
public ColumnDefinition(string name = "", int width = 80)
{
ColumnName = name;
ColumnWidth = width;
}
public string ColumnName { get; set; }
private int columnWidth;
public int ColumnWidth
{
get { return columnWidth; }
set { columnWidth = value; OnPropertyChanged("ColumnWidth"); }
}
}
}
MainWindow:
namespace WpfApplication_async
{
public partial class MainWindow : Window
{
MySection sec1,sec2;
bool isSec1 = true;
public MainWindow()
{
InitializeComponent();
sec1 = new MySection();
for(int i=0;i<50;i++){
MyRecord rec = new MyRecord();
rec.Key = i.ToString();
for(int j=0;j<20;j++){
rec.Items.Add(new StringValue("abc"));
rec.Items[rec.Items.Count - 1].ColumnDef = new ColumnDefinition(j.ToString());
}
sec1.Records.Add(rec);
}
sec2 = new MySection();
for (int i = 0; i < 50; i++)
{
MyRecord rec = new MyRecord();
rec.Key = i.ToString();
for (int j = 0; j < 20; j++)
{
rec.Items.Add(new ListValue(new List<string> { "a", "b" }, new List<string> { "a", "b" }, "a"));
rec.Items[rec.Items.Count - 1].ColumnDef = new ColumnDefinition(j.ToString());
}
sec2.Records.Add(rec);
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
if (isSec1)
//listView.DataContext = sec2;
listView.DataContext = new AsyncVirtualizingCollection<MyRecord>(sec2, 10, 30 * 1000);
else
//listView.DataContext = sec1;
listView.DataContext = new AsyncVirtualizingCollection<MyRecord>(sec1, 10, 30 * 1000);
isSec1 = !isSec1;
}
}
}
I found a simple solution. Instead of setting DataContext, I added items manually with low priority. UI does not freeze. Goal is achieved.
foreach (var r in sec.Records)
{
listView.Dispatcher.Invoke((new Action(delegate()
{
listView.Items.Add(r);
})), DispatcherPriority.SystemIdle);
}