Dynamically changing tabitem via button command not working wfp - c#

Im trying to create a function in a wpf program, where I can select an item in a listview, and press a button and it changes the tabitem and allows me to then edit the item from the listview that was selected. Im having issues with getting the tabitem to change for me.
For the navigation of my app, I have a ViewModelBase, which my AppointmentsViewModel inherits from. Inside the AppointmentsViewVM there is a tabcontrol with 4 items, by clicking each one it loads the requested view/viewmodel for that function.
This is not the only way I've tried to get this to work, Im currently on day 4. I could get the TabIndex to change in the TabControl earlier, but the tab would still not change for me. So I abandoned that and tried the below route (still no luck).
ViewModelBase
namespace MBR2.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public ICommand MainMenuViewDogs_Command { get; set; }
public ICommand MainMenuViewAppointments_Command { get; set; }
private object _SelectedViewModel;
public object SelectedViewModel
{
get { return _SelectedViewModel; }
set
{
_SelectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
public ViewModelBase()
{
MainMenuViewDogs_Command = new BaseCommand(OpenDogs);
MainMenuViewAppointments_Command = new BaseCommand(OpenAppointments);
}
private void OpenDogs(object obj)
{
SelectedViewModel = new DogsViewModel();
}
private void OpenAppointments(object obj)
{
SelectedViewModel = new AppointmentsViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool _SelectedIndexView;
public bool SelectedIndexView
{
get { return _SelectedIndexView; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexView");
}
}
private bool _SelectedIndexAdd;
public bool SelectedIndexAdd
{
get { return _SelectedIndexAdd; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexAdd");
}
}
private bool _SelectedIndexEdit;
public bool SelectedIndexEdit
{
get { return _SelectedIndexEdit; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexEdit");
}
}
private bool _SelectedIndexDelete;
public bool SelectedIndexDelete
{
get { return _SelectedIndexDelete; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexDelete");
}
}
}
}
AppointmentsViewModel
{
public class AppointmentsViewModel : ViewModelBase
{
private AppointmentsAddVM _AppointmentsAddVM;
public AppointmentsAddVM AppointmentsAddVM { get { return _AppointmentsAddVM; } }
private AppointmentsEditVM _AppointmentsEditVM;
public AppointmentsEditVM AppointmentsEditVM { get { return _AppointmentsEditVM; } }
private AppointmentsDeleteVM _AppointmentsDeleteVM;
public AppointmentsDeleteVM AppointmentsDeleteVM { get { return _AppointmentsDeleteVM; } }
private AppointmentsViewVM _AppointmentsViewVM;
public AppointmentsViewVM AppointmentsViewVM { get { return _AppointmentsViewVM; } }
public ObservableCollection<object> ViewModelList { get; set; }
public AppointmentsViewModel()
{
this.ViewModelList = new ObservableCollection<object>();
_AppointmentsAddVM = new AppointmentsAddVM();
_AppointmentsEditVM = new AppointmentsEditVM();
_AppointmentsDeleteVM = new AppointmentsDeleteVM();
_AppointmentsViewVM = new AppointmentsViewVM();
this.ViewModelList.Add(_AppointmentsAddVM);
this.ViewModelList.Add(_AppointmentsEditVM);
this.ViewModelList.Add(_AppointmentsDeleteVM);
this.ViewModelList.Add(_AppointmentsViewVM);
}
}
}
AppointmentsView.xaml
<UserControl
x:Class="MBR2.Views.AppointmentsView"
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:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:vms="clr-namespace:MBR2.ViewModels.Appointments"
xmlns:views="clr-namespace:MBR2.Views.Appointments"
xmlns:viewmodels="clr-namespace:MBR2.ViewModels"
d:DataContext="{d:DesignInstance Type=viewmodels:AppointmentsViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vms:AppointmentsViewVM}">
<views:AppointmentsViewView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsAddVM}">
<views:AppointmentsAddView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsDeleteVM}">
<views:AppointmentsDeleteView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsEditVM}">
<views:AppointmentsEditView />
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="Appointments" Width="Auto" Height="Auto">
<DockPanel HorizontalAlignment="Center"
Height="Auto"
LastChildFill="False"
VerticalAlignment="Top"
Width="Auto">
<TabControl x:Name="VMTabControl">
<TabItem x:Name="ViewTab"
TabIndex="0"
Header="View"
IsSelected="{Binding SelectedIndexView}"
Content="{Binding AppointmentsViewVM}"></TabItem>
<TabItem x:Name="AddTab"
TabIndex="1"
Header="Add"
IsSelected="{Binding SelectedIndexAdd}"
Content="{Binding AppointmentsAddVM}"></TabItem>
<TabItem x:Name="EditTab"
TabIndex="2"
Header="Edit"
IsSelected="{Binding SelectedIndexEdit}"
Content="{Binding AppointmentsEditVM}"></TabItem>
<TabItem x:Name="DeleteTab"
TabIndex="3"
Header="Delete"
IsSelected="{Binding SelectedIndexDelete}"
Content="{Binding AppointmentsDeleteVM}"></TabItem>
</TabControl>
</DockPanel>
</Grid>
</UserControl>
And the associated AppointmentsViewVM
namespace MBR2.ViewModels.Appointments
{
public class AppointmentsViewVM : ViewModelBase, INotifyPropertyChanged
{
private List<AppointmentsView_Wrapper> _AppointmentsView;
public List<AppointmentsView_Wrapper> AppointmentsView
{
get { return _AppointmentsView; }
set
{
_AppointmentsView = value;
OnPropertyChanged("AppointmentsView");
}
}
private List<string> _NameColumn = new List<string>();
public List<string> NameColumn
{
get { return _NameColumn; }
set
{
_NameColumn = value;
OnPropertyChanged("NameColumn");
}
}
private List<string> _ApptDateColumn = new List<string>();
public List<string> ApptDateColumn
{
get { return _ApptDateColumn; }
set
{
_ApptDateColumn = value;
OnPropertyChanged("ApptDateColumn");
}
}
private List<string> _ApptTimeColumn = new List<string>();
public List<string> ApptTimeColumn
{
get { return _ApptTimeColumn; }
set
{
_ApptTimeColumn = value;
OnPropertyChanged("ApptTimeColumn");
}
}
private List<string> _ApptVetColumn = new List<string>();
public List<string> ApptVetColumn
{
get { return _ApptVetColumn; }
set
{
_ApptVetColumn = value;
OnPropertyChanged("ApptVetColumn");
}
}
private List<string> _ApptCreatedColumn = new List<string>();
public List<string> ApptCreatedColumn
{
get { return _ApptCreatedColumn; }
set
{
_ApptCreatedColumn = value;
OnPropertyChanged("ApptCreatedColumn");
}
}
private List<int> _ApptIDColumn = new List<int>();
public List<int> ApptIDColumn
{
get { return _ApptIDColumn; }
set
{
_ApptIDColumn = value;
OnPropertyChanged("ApptIDColumn");
}
}
private string _AppointmentEdit_Enabled = "False";
public string AppointmentEdit_Enabled
{
get { return _AppointmentEdit_Enabled; }
set
{
_AppointmentEdit_Enabled = value;
OnPropertyChanged("AppointmentEdit_Enabled");
}
}
private AppointmentsView_Wrapper _ApptIDSelected;
public AppointmentsView_Wrapper ApptIDSelected
{
get { return _ApptIDSelected; }
set
{
AppointmentEdit_Enabled = "True";
_ApptIDSelected = value;
OnPropertyChanged("ApptIDSelected");
}
}
public AppointmentData AppointmentData = new AppointmentData();
public Messaging Messaging = new Messaging();
public ICommand AppointmentsListView_Command => new DelegateCommand<object>(AppointmentsListView_Clicked);
public ICommand EditSelection_Command => new DelegateCommand<object>(EditSelection_Clicked);
public AppointmentsViewVM()
{
BuildPage();
}
public async void BuildPage()
{
AppointmentsView = await AppointmentData.Appointments_GetAll();
foreach(var item in AppointmentsView)
{
ApptIDColumn.Add(item.ApptID);
NameColumn.Add(item.DogName);
ApptDateColumn.Add(item.ApptDate);
ApptTimeColumn.Add(item.ApptTime);
ApptVetColumn.Add(item.ApptVet);
ApptCreatedColumn.Add(item.ApptCreated.ToString("dd/mm/yyyy"));
}
}
public void AppointmentsListView_Clicked(object obj)
{
Messaging.ShowAlert(ApptIDSelected.ApptID.ToString());
}
public void EditSelection_Clicked(object obj)
{
bool result = Messaging.AskQuestion(ApptIDSelected.ApptID.ToString());
if(result)
{
SelectedIndexView = false;
SelectedIndexAdd = false;
SelectedIndexEdit = true;
SelectedIndexDelete = false;
OnPropertyChanged("SelectedIndexView");
OnPropertyChanged("SelectedIndexAdd");
OnPropertyChanged("SelectedIndexEdit");
OnPropertyChanged("SelectedIndexDelete");
}
else
{
Messaging.ShowAlert("no");
}
}
}
}

Here's a minimal reproduction of something where you select in a listbox and that then selects a corresponding tab in a tabcontrol.
This is very minimal but we can perhaps imagine a more sophisticated viewmodel per item in the listbox with name and viewmodel or something.
This is mainwindow.
<Window.Resources>
<DataTemplate DataType="{x:Type local:Avm}">
<local:Aview/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Bvm}">
<local:Bview/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Cvm}">
<local:Cview/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewmodel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding ViewModels}"
x:Name="lb"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ViewModelName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TabControl Grid.Column="1"
ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding ElementName=lb, Path=SelectedItem, Mode=TwoWay}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding ViewModelName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
I have only done 3 views and viewmodels.
Note that the selecteditem of the listbox is bound twoway to the tabcontrol.
I have matching views and viewmodels A, B and C
MainWindowViewModel
public class MainWindowViewmodel : ViewModelBase
{
public ObservableCollection<Object> ViewModels { get; set; } = new ObservableCollection<Object>{
new Avm{ViewModelName="A viewmodel" },
new Bvm{ViewModelName="B viewmodel" },
new Cvm{ViewModelName="C viewmodel" }
};
}
Both the itemssource of listbox and tabcontrol are bound to that collection of viewmodels. Which are, as I mentioned, as simple as you get really.
Viewmodelbase
public class ViewModelBase : INotifyPropertyChanged
{
public string ViewModelName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Avm, Bvm and Cvm just inherit from that.
An example usercontrol view.
<Grid>
<TextBlock Text="{Binding ViewModelName}"/>
</Grid>
</UserControl>
When I spin that up, select and select an item in the listbox the matching tab is selected. And vice versa. Select a tab and it selects the same one in the listbox.

Related

Cannot update ListView using ObservableCollection methods via a derived class

There are two views, the first view contains a ListView along with 2 buttons for adding and removing a person. View 2 shows the selected view by using a ContentControl, when the Add button is clicked it shows a form with the name, gender, …, and a button to store the information and display the person name in the ListView.
When I try to remove a person from the ListView it works since the button is within the same view/context of the ListView. But when I call the Add method via AddViewModel it does not update the UI.
Below it is available the relevant parts code of the different classes.
View sample
View1.xaml
<Window.Resources>
<vm:ViewModelBase x:Key="ViewModel"/>
<DataTemplate x:Name="addViewTemplate" DataType="{x:Type vm:AddViewModel}">
<views:AddView/>
</DataTemplate>
</Window.Resources>
<Button Command="{Binding change2addViewCommand,
Source={StaticResource ViewModel}}">
<materialDesign:PackIcon Kind="Plus" Width="12"/>
</Button>
<Button Command="{Binding removeFromListCommand, Source={StaticResource ViewModel}}"
CommandParameter="{Binding ElementName=lvNames, Path=SelectedItem}">
<materialDesign:PackIcon Kind="Delete" Width="12"/>
</Button>
<ListView MinHeight="560"
DataContext="{Binding Source={StaticResource ViewModel}}"
ItemsSource="{Binding}"
x:Name="lvNames" >
</ListView>
<ContentControl x:Name="controllerViews" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="4"
Grid.RowSpan="5" Content="{Binding CurrentView, Mode=TwoWay}"/>
ViewModelBase.cs
public class ViewModelBase: ObservableCollection<Person>, INotifyPropertyChanged
{
private ViewModelBase currentView;
public ViewModelBase CurrentView
{
get
{
return currentView;
}
set
{
currentView = value;
OnPropertyChanged("CurrentView");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public ViewModelBase()
{
for (int i = 1; i <= 2; i++)
{
Add(new Person()
{
Name = "Person" + i,
Age = i,
Gender = Gender.Female,
Comments = "",
Photo_Path = ""
});
}
change2addViewCommand = new RelayCommand(ExecuteAddCommand, CanExectuteAddCommand);
removeFromListCommand = new RelayCommand(ExecuteRemoveCommand, CanExectuteRemoveCommand);
}
public event PropertyChangedEventHandler PropertyChanged;
public bool CanExectuteAddCommand(object param)
{
return true;
}
public void ExecuteAddCommand(object param)
{
CurrentView = new AddViewModel();
}
public bool CanExectuteRemoveCommand(object param)
{
return true;
}
public void ExecuteRemoveCommand(object param)
{
Person player = param as Person;
Remove(player); //Working
}
protected void AddToListView(Person player)
{
Add(player) // Not Working
}
}
}
AddViewModel.cs
public class AddViewModel: ViewModelBase
{
public RelayCommand addViewCheckCommand { get; set; }
public AddViewModel()
{
addViewCheckCommand = new RelayCommand(ExecuteCheckCommand, CanExecuteCheckCommand);
}
private bool CanExecuteCheckCommand(object param)
{
return true;
}
private void ExecuteCheckCommand(object param)
{
BocciaPlayer player = param as Person;
AddToListView(player); // Inherited method
}
}

Why invoking command when `TreeViewItem` is expanded doesn't work?

I'm trying to invoke command when TreeViewItem is expanded as explained here, but for some reason it doesn't work. I think it's because of HierarchicalDataTemplate, but I don't know why.
Does any one have an idea what's the problem?
XAML
<Window x:Class="MyProject.MainWindow"
...
xmlns:local="clr-namespace:MyProject"
xmlns:bindTreeViewExpand="clr-namespace:MyProject"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TreeView ItemsSource="{Binding RootFolders}">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="bindTreeViewExpand:Behaviours.ExpandingBehaviour" Value="{Binding ExpandingCommand}"/>
</Style>
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type local:DriveFolder}">
<TreeViewItem Header="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Window>
BEHAVIOURS
namespace GooglePhotoPermissions
{
public static class Behaviours
{
#region ExpandingBehaviour (Attached DependencyProperty)
public static readonly DependencyProperty ExpandingBehaviourProperty =
DependencyProperty.RegisterAttached("ExpandingBehaviour", typeof(ICommand), typeof(Behaviours),
new PropertyMetadata(OnExpandingBehaviourChanged));
public static void SetExpandingBehaviour(DependencyObject o, ICommand value)
{
o.SetValue(ExpandingBehaviourProperty, value);
}
public static ICommand GetExpandingBehaviour(DependencyObject o)
{
return (ICommand)o.GetValue(ExpandingBehaviourProperty);
}
private static void OnExpandingBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeViewItem tvi = d as TreeViewItem;
if (tvi != null)
{
ICommand ic = e.NewValue as ICommand;
if (ic != null)
{
tvi.Expanded += (s, a) =>
{
if (ic.CanExecute(a))
{
ic.Execute(a);
}
a.Handled = true;
};
}
}
}
#endregion
}
}
ViewModel
namespace MyProject
{
public class DriveFile
{
public string Name { get; set; }
public string Id { get; set; }
public bool IsFolder { get; protected set; }
public DriveFile()
{
IsFolder = false;
}
}
public class DriveFolder : DriveFile
{
public List<DriveFile> Children { get; set; }
public DriveFolder()
{
IsFolder = true;
Children = new List<DriveFile>();
}
}
public class DriveViewModel
{
public IList<DriveFolder> RootFolders
{
get
{
return GetRootFolders();
}
}
private ICommand _expandingCommand;
public ICommand ExpandingCommand
{
get
{
if (_expandingCommand == null)
{
_expandingCommand = new RelayCommand(Foo);
}
return _expandingCommand;
}
}
private DriveService _driveService;
private IList<DriveFolder> GetRootFolders()
{
...
}
}
}
a
Your binding is wrong.
You define a binding in a style that applies to each TreeViewItem. In this binding, the source is the DataContext of each TreeViewItem itself. That would be a DriveFolder or a DriveFile object.
Of course, these objects have no ExpandingCommand property, so your binding just fails.
Change your binding in such a manner that the DataContext of the TreeView is used as source (to access your view model and its command). You can use ElementName or RelativeSource, e.g. like this:
<Setter Property="bindTreeViewExpand:Behaviours.ExpandingBehaviour"
Value="{Binding DataContext.ExpandingCommand, RelativeSource={RelativeSource AncestorType=TreeView}}"/>

Textbox in listbox not updating when lostfocus

I have a textbox inside of a listbox that I would like to update the ObservableCollection when the textbox loses focus. I tried using my collections CollectionChanged event as described in this post here to try to solve the problem. Right now the only way to update the collection is if I add or remove an item from the listbox. Am I going about this the wrong way? What am I missing for the textbox to update the collection?
MainWindow.xaml
<ListBox ItemsSource="{Binding DataLogList,Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding DataLogLabel}" Margin="5"/>
<TextBox Text="{Binding DataLogName,Mode=TwoWay,UpdateSourceTrigger=LostFocus}" Margin="5" Width="150"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
MainViewModel.cs
public MainViewModel()
{
DataLogList = new ObservableCollection<DataLogContent>();
DataLogList.CollectionChanged += (s, e) =>
{
if (e.NewItems != null)
{
foreach (DataLogContent item in e.NewItems)
{
item.PropertyChanged += item_PropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (DataLogContent item in e.OldItems)
{
item.PropertyChanged -= item_PropertyChanged;
}
}
};
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged();
}
DataLogContent.cs
public class DataLogContent:ViewModelBase
{
private string dataLogLabel;
public string DataLogLabel
{
get { return this.dataLogLabel; }
set
{
this.dataLogLabel = value;
NotifyPropertyChanged();
}
}
private string dataLogName;
public string DataLogName
{
get { return this.dataLogName; }
set
{
this.dataLogLabel = value;
NotifyPropertyChanged();
}
}
}
I have it working based on this. My guess is that you may be over complicating the adding/removing logic of an item inside the ObservableCollection. There's no need to monitor the property changed event, as each object will already raise the event whenever a property within it changes.
Here's what I have:
namespace WpfApplication1
{
public class MainViewModel : ViewModelBase
{
public ObservableCollection<DataLogContent> DataLogList { get; private set; }
public MainViewModel()
{
DataLogList = new ObservableCollection<DataLogContent>();
DataLogList.Add(new DataLogContent
{
DataLogLabel = "Label",
DataLogName = "Name"
});
DataLogList.Add(new DataLogContent
{
DataLogLabel = "Label2",
DataLogName = "Name2"
});
}
}
public class DataLogContent : ViewModelBase
{
private string dataLogLabel;
public string DataLogLabel
{
get { return this.dataLogLabel; }
set
{
this.dataLogLabel = value;
OnPropertyChanged("DataLogLabel");
}
}
private string dataLogName;
public string DataLogName
{
get { return this.dataLogName; }
set
{
this.dataLogName = value;
OnPropertyChanged("DataLogName");
}
}
}
}
Simple ViewModelBase:
namespace WpfApplication1
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(property));
}
}
}
}
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding DataLogList,Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding DataLogLabel}" Margin="5"/>
<TextBox Text="{Binding DataLogName,Mode=TwoWay,UpdateSourceTrigger=LostFocus}" Margin="5" Width="150"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>

WPF Record didn't update even with the use of INotifyPropertyChanged

I have a collection of Patients which I set in my ComboBox, whenever I ran and test the form, it seems fine, but whenever I update a record or add another one (from another form), the ComboBox doesn't get updated. I can do a remedy to this by using the code behind and IContent interface but I like to reduce the use of code behind as much as possible. Can you pinpoint to me what markup or code that is lacking?
Here is my Grid Markup (I omitted the RowDefinitions and ColumnDefinitions)
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
<businessLogic:PatientMgr x:Key="PatientsViewModel" />
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid" DataContext="{StaticResource ItemsSource}">
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage"
VerticalAlignment="Center"
HorizontalAlignment="Center"
StretchDirection="Both" Stretch="Uniform"
Source="{Binding ElementName=FullNameComboBox, Path=SelectedItem.PictureId, Converter={StaticResource ToImageSourceConverter}, UpdateSourceTrigger=PropertyChanged}"
/>
</Border>
<TextBox x:Name="IdTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.Id, UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" Grid.Column="1" Visibility="Collapsed"/>
<ComboBox x:Name="FullNameComboBox" Grid.Row="10" Grid.Column="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath = "FullName"
SelectedIndex="0"
SelectionChanged="FullNameComboBox_OnSelectionChanged"
/>
<TextBox x:Name="GenderTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.GenderName, UpdateSourceTrigger=PropertyChanged}" Grid.Row="11" Grid.Column="1" IsReadOnly="True" IsReadOnlyCaretVisible="True"/>
</Grid>
Here is my BusinessLogicLayer
public class PatientMgr :INotifyPropertyChanged
{
#region Fields
private readonly PatientDb _db;
private Patient _entity;
private List<Patient> _entityList;
private ObservableCollection<Patient> _comboBoxItemsCollection;
private Patient _selectedItem;
#endregion
#region Properties
public Patient Entity
{
get { return _entity; }
set
{
if (Equals(value, _entity)) return;
_entity = value;
OnPropertyChanged();
}
}
public List<Patient> EntityList
{
get { return _entityList; }
set
{
if (Equals(value, _entityList)) return;
_entityList = value;
OnPropertyChanged();
}
}
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
public Patient SelectedItem
{
get { return _selectedItem; }
set
{
if (Equals(value, _selectedItem)) return;
_selectedItem = value;
OnPropertyChanged();
}
}
#endregion
#region Constructor
public PatientMgr()
{
_db = new PatientDb();
Entity = new Patient();
EntityList = new List<Patient>();
Parameters = new Patient();
ComboBoxItemsCollection = new ObservableCollection<Patient>(_db.RetrieveMany(Entity));
SelectedItem = ComboBoxItemsCollection[0];
}
#endregion
public List<Patient> RetrieveMany(Patient parameters)
{
return _db.RetrieveMany(parameters);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Because ItemsSource used statically, your ComboBox doesn't get notified when it's source changed. Try using an instance of PatientMgr as your UserControl.DataContext like this:
public partial class YourUserControl: UserControl
{
private PatientMgr PatientsViewModel;
public YourUserControl()
{
InitializeComponent();
PatientsViewModel = new PatientMgr();
DataContext = PatientsViewModel;
}
public void AddComboItem(Patient item)
{
PatientsViewModel.ComboBoxItemsCollection.Add(item);
}
public void UpdateComboItem(Patient item)
{
//your update code
}
After removing static bindings, your XAML should looks like this:
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid"
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage" ...
Since ComboBoxItemsCollection is an ObservableCollection, it notifies UI on add/remove automatically, however you have to write proper update functionality in order to force UI to refresh.
EDIT:
To implement an update method by taking advantage of Editable feature of ComboBox you could follow these steps:
Defined an integer property to track the last valid index (index != -1) of combo-box selected item and bind it to FullNameComboBox.SelectedIndex.
Defined a string property that represents the text value of combo-box selected item and bind it to FullNameComboBox.Text (the binding should trigger on LostFocus so the changes only accepted when user finished typing).
Find and remove ComboBoxItemsCollection.OldItem (find it by last valid index) and insert ComboBoxItemsCollection.ModifiedItem when FullNameComboBox.Text changes. Using remove and insert instead of assigning (OldItem = ModifiedItem;) will force UI to update.
In PatientMgr Code:
public int LastValidIndex
{
get { return _lastIndex; }
set
{
if (value == -1) return;
_lastIndex = value;
OnPropertyChanged();
}
}
public string CurrentFullName
{
get
{
return SelectedItem.FullName;
}
set
{
var currentItem = SelectedItem;
ComboBoxItemsCollection.RemoveAt(LastValidIndex);
currentItem.FullName = value;
ComboBoxItemsCollection.Insert(LastValidIndex, currentItem);
SelectedItem = currentItem;
}
}
In UserControl.Xaml :
<ComboBox x:Name="FullNameComboBox" Grid.Row="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection,
UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem}"
SelectedIndex="{Binding LastValidIndex}"
IsTextSearchEnabled="False"
Text="{Binding CurrentFullName, UpdateSourceTrigger=LostFocus}"
DisplayMemberPath = "FullName"
/>
I don’t like this:
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
Try this:
OnPropertyChanged(“ComboBoxItemsCollection”);
And, are you sure this equals is resolved right?
if (Equals(value, _comboBoxItemsCollection)) return;
Try to debug it …
Your problem has to do with that IsEditable="True" set on the ComboBox. Your bindings work fine for me.
Here is what i've just tried:
Simple Patient model with its FullName property:
public class Patient : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _FullName;
public string FullName
{
get { return _FullName; }
set
{
_FullName = value;
PropertyChanged(this, new PropertyChangedEventArgs("FullName"));
}
}
}
This is the XAML:
<Window x:Class="PatientsStack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PatientsStack"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:PatientsMgr x:Key="PatientsViewModel"/>
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</Window.Resources>
<Grid DataContext="{StaticResource ItemsSource}">
<StackPanel>
<ComboBox x:Name="FullNameComboBox" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
TextSearch.TextPath="FullName"
DisplayMemberPath="FullName"
SelectedItem="{Binding SelectedPatient}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
VerticalAlignment="Top"
IsTextSearchEnabled="False"
SelectedIndex="0">
</ComboBox>
<Button Content="Change name" Command="{Binding ChangeNameCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
<Button Content="Add patient" Command="{Binding AddPatientCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
</StackPanel>
</Grid>
This two pieces right here did the job:
SelectedItem="{Binding SelectedFilter}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
Without the Text binding, the updated worked, but you didn't see it unless you click the drop down.
As you see, i have 2 command to change one existing Patient's FullName or to add a new one. Both work as expected.
Here is the ViewModel for this:
public class PatientsMgr : INotifyPropertyChanged
{
private ObservableCollection<Patient> _ComboBoxItemsCollection;
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get
{
return _ComboBoxItemsCollection;
}
set
{
_ComboBoxItemsCollection = value;
PropertyChanged(this, new PropertyChangedEventArgs("ComboBoxItemsCollection"));
}
}
private Patient _SelectedPatient;
public Patient SelectedPatient
{
get { return _SelectedPatient; }
set
{
_SelectedPatient = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedPatient"));
}
}
public ICommand ChangeNameCommand { get; set; }
public ICommand AddPatientCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public PatientsMgr()
{
ComboBoxItemsCollection = new ObservableCollection<Patient>();
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient1" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient2" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient3" });
ChangeNameCommand = new RelayCommand<Patient>(ChangePatientName);
AddPatientCommand = new RelayCommand<Patient>(AddPatient);
}
public void ChangePatientName(Patient patient)
{
patient.FullName = "changed at request";
}
public void AddPatient(Patient p)
{
ComboBoxItemsCollection.Add(new Patient() { FullName = "patient added" });
}
}
I am posting my RelayCommand too, but i am sure you have it defined for your actions:
public class RelayCommand<T> : ICommand
{
public Action<T> _TargetExecuteMethod;
public Func<T, bool> _TargetCanExecuteMethod;
public RelayCommand(Action<T> executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public bool CanExecute(object parameter)
{
if (_TargetExecuteMethod != null)
return true;
return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
T tParam = (T)parameter;
if (_TargetExecuteMethod != null)
_TargetExecuteMethod(tParam);
}
}

How to bind listview to custom item collection

How do I get my custom item collection to show up in my list view using WPF data bindings?
I have a tried to make a ViewModel and a custom collection that the ViewModel manipulates, in an attempt to get this collection to show up in a listview. I have a view model and a custom collection and a custom item class:
public class TranslationViewModel
{
public TranslationViewModel() { this.translatedItems = new TransListboxCollection(); }
public TransListboxCollection translatedItems { get; private set; }
public void addTranslatedItem(TransListboxItem message)
{
translatedItems.Add(message);
}
}
public class TransListboxCollection : BindingList<TransListboxItem>
{
public TransListboxCollection()
{
//initialize
}
}
public class TransListboxItem : INotifyPropertyChanged
{
private String _rawString;
private String _tString;
public String rawString
{
get { return _rawString; }
set { _rawString = value; NotifyPropertyChanged("rawString"); }
}
public String tString
{
get { return _tString; }
set { _tString = value; NotifyPropertyChanged("tString"); }
}
public TransListboxItem(String value)
{
this.tString = value;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public override string ToString()
{
return this.tString;
}
}
I have a WPF element hosted in a windows form
public partial class wGlobal : UserControl
{
public TranslationViewModel tvm { get; set; }
public wGlobal()
{
InitializeComponent();
this.DataContext = tvm;
}
}
The XAML code for such
<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:local="clr-namespace:MHF_Transcoder_3" x:Class="MHF_Transcoder_3.wGlobal"
mc:Ignorable="d"
d:DesignWidth="1000" d:DesignHeight="150">
<Grid Width="1000" Height="150">
<ListView x:Name="listView1" ItemsSource="{Binding tvm}" HorizontalAlignment="Left" Width="1000" Height="150" VerticalAlignment="Top" Background="Black" Foreground="White" RenderTransformOrigin="0.5,0.5">
<DataTemplate>
<TextBlock Text="{Binding tString}" ToolTipService.ToolTip="{Binding rawString}" />
</DataTemplate>
</ListView>
</Grid>
and I have that element hosted in a windows form control
public partial class frmGlobal : Form
{
wGlobal xamlForm;
TranslationViewModel tvm;
public frmGlobal()
{
InitializeComponent();
tvm = new TranslationViewModel();
xamlForm = (wGlobal)elementHost1.Child;
xamlForm.tvm = tvm;
}
delegate void addMessageCallback(TransListboxItem message);
public void addMessage(TransListboxItem message) {
tvm.addTranslatedItem(message);
}
}
When I get the program up and launch everything, all my list view says is "System.Windows.DataTemplate". I've never really worked with WPF or data bindings before. I'm open to any and all advice and suggestions. Please help me get this setup and properly working.
Wrap Datatemplate with ItemTemplate
<ListView x:Name="listView1" ItemsSource="{Binding translatedItems}" HorizontalAlignment="Left" Width="1000" Height="150" VerticalAlignment="Top" Background="Black" Foreground="White" RenderTransformOrigin="0.5,0.5">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding tString}" ToolTipService.ToolTip="{Binding rawString}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Also as the tvm is the datacontext, you must bind to the collection "translatedItems"

Categories

Resources