I have two windows (views) and they share the same viewmodel. I use one window to input data and the second to order it in 3 different listboxes (observablecollections) based on the value of one combobox in the input window. The problem I have is that the input doesn't show up in the listboxes. it's like the observable collection doesn't update.
This is the code I have in the input window
public partial class AddPerson : Window
{
public NetworkViewModel MyViewModel;
public AddPerson()
{
InitializeComponent();
this.DataContext = new NetworkViewModel();
}
}
and in xaml
<TextBox x:Name="tb_firstName" Text="{Binding Path=FirstName, Mode=TwoWay}"/>
<ComboBox x:Name="cb_group" ItemsSource="{Binding GroupLevel}" SelectedItem="{Binding SelectedGroupLevel}"/>
in the displaywindow with the 3 listboxes I have
public partial class Display : Window
{
public NetworkViewModel MyViewModel;
public AddPerson()
{
InitializeComponent();
this.DataContext = new NetworkViewModel();
}
}
and this is the xaml for the display window
<ListBox x:Name="lb1" GotFocus="textBox1_visible" ItemsSource="{Binding NetworkList1}" dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True" SelectedItem ="{Binding Path=SelectedItemGroup, Mode=TwoWay}" />
<ListBox x:Name="lb2" GotFocus="textBox1_visible" ItemsSource="{Binding NetworkList2}" dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True" SelectedItem ="{Binding Path=SelectedItemGroup, Mode=TwoWay}" />
This is what my viewmodel looks like
public class NetworkViewModel: INotifyPropertyChanged
{
private ObservableCollection<Person> _networkList1 = new ObservableCollection<Person>();
public ObservableCollection<Person> NetworkList1 //Binds with the listbox
{
get { return _networkList1; }
set { _networkList1 = value; }
}
private string _firstName;
public string FirstName
{
get { return this._firstName; }
set { this._firstName = value; NotifyPropertyChanged("FirstName"); }
}
private string _group;
public string Group
{
get { return _group; }
set { _group = value; NotifyPropertyChanged("Group"); }
}
public NetworkViewModel()
{
AddPersonCommand = new RelayCommand(AddPerson);
}
private ICommand _addPersonCommand;
public ICommand AddPersonCommand
{
get { return _addPersonCommand; }
set { _addPersonCommand = value; }
}
public void AddPerson(object obj)
{
if (SelectedGroupLevel == "Primary")
{
NetworkList1.Add(new Person()
{
FirstName = this.FirstName,
Group = this.Group,
});
MessageBox.Show("Person successfully added");
}
else if (SelectedGroupLevel == "Secondary")
{
NetworkList2.Add(new Person()
{
FirstName = this.FirstName,
Group = this.Group,
});
MessageBox.Show("Person successfully added");
}
}
When I click the add button in the input window I can see the message in the messagebox, which means that the data is added to the observable collection, but I don't see it displayed, Hopefully somebody can help me with this issue. I'm very green to wpf and mvvm. Thanks.
Problem lies in the way you are using view models. You are creating two separate instances of your view model one in the AddPerson view and one in the Display view. When you add an item in the AddPerson view it is updating its own view model not the Display view's view model.
You need a way two communicate the changes from one view model to the other.
Related
I'm using Wpf MVVM, if i know the index number of item/row then how can i search the value in listview/itemsource by specific index number.
Note: i can get index number, index number will already be known.
below is the xaml code for listview
<ListView
Grid.Row="1"
ItemContainerStyle="{StaticResource FileItemStyle}"
ItemsSource="{Binding BarCode, IsAsync=True}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedIndex="{Binding SelectedIndex}"
SelectedItem="{Binding SelectBarCode,
UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Single"
Style="{StaticResource ListItemsMain}"
and ObservableCollection for itemsource
private ObservableCollection<BarCodeModel> mBarCode = null;
public ObservableCollection<BarCodeModel> BarCode
{
get
{
mBarCode = mBarCode ?? new ObservableCollection<BarCodeModel>();
return mBarCode;
}
}
and below code is for model
public class BarCodeModel
{
public int BarCodeEntry_ID { get; set; }
public string BarCodeEntry_Title { get; set; }
and below is the command where i want to put my logic
private ICommand mSearchValueByIndexNumberCommand;
public ICommand SearchValueByIndexNumberCommand
{
get
{
if (mSearchValueByIndexNumberCommand == null)
{
mSearchValueByIndexNumberCommand = new DelegateCommand(delegate ()
{
// search BarCodeEntry_ID in BarCode where SelectedIndex is 5 (or other value)
});
}
return mSearchValueByIndexNumberCommand;
}
}
As in your XAML you bind your ListView.ItemsSource to BarCode of your ViewModel, but also bind ListView.SelectedItem and ListView.SelectedIndex to SelectBarCode and SelectedIndex, now, when you select some Item in a ListView, it (ListView) will update values of SelectBarCode and SelectedIndex in your ViewModel.
So, you can access your current selection with SelectBarCode or BarCode[SelectedIndex].
Below is answer to my question, a special thanks to #vasily.sib
private ICommand mSearchValueByIndexNumberCommand;
public ICommand SearchValueByIndexNumberCommand
{
get
{
if (mSearchValueByIndexNumberCommand == null)
{
mSearchValueByIndexNumberCommand = new DelegateCommand(delegate ()
{
int BarCodeId = BarCode[SelectedIndex].BarCodeEntry_ID;
});
}
return mSearchValueByIndexNumberCommand;
}
}
I'm trying to figure out why I can't set the initial SelectedItem value on my ComboBox if I bind with ItemsSource="{x:Bind [source]}".
This xaml works
<ComboBox
ItemsSource="{Binding Sites, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedContractSite, Mode=TwoWay}"/>
But when I change to the following xaml, the ComboBox contains the sites, but does not show the SelectedItem as the default. (In fact, it appears to flicker into view and then disappear).
<ComboBox
ItemsSource="{x:Bind ViewModel.Sites, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedContractSite, Mode=TwoWay}"/>
Here is the relevant code in the ViewModel. (I abbreviated the long Sites list.)
public List<string> Sites
{
get
{
return new List<string>()
{
"Miami",
"Texas"
};
}
}
private string _selectedContractSite = "Texas";
public string SelectedContractSite
{
get
{
return _selectedContractSite;
}
set
{
Set(ref _selectedContractSite, value);
}
}
Thanks for the help!
The issue appears to be related to code you haven't shown. (For future reference please see https://stackoverflow.com/help/mcve to remove guesswork in answering future questions.)
If I create a viewModel like this
public class ViewModel : INotifyPropertyChanged
{
public List<string> Sites
{
get
{
return new List<string>()
{
"Miami",
"Texas"
};
}
}
private string _selectedContractSite = "Texas";
public string SelectedContractSite
{
get
{
return _selectedContractSite;
}
set
{
if (_selectedContractSite != value)
{
_selectedContractSite = value;
OnPropertyChanged(nameof(SelectedContractSite));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
and then set up the codebehind like this:
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new ViewModel();
}
public ViewModel ViewModel { get; set; }
Then the following XAML works as expected
<ComboBox ItemsSource="{x:Bind ViewModel.Sites, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedContractSite, Mode=TwoWay}" />
Note. I'm using x:Bind and referencing the ViewModel in both binding paths.
I suspect your confusion lies in the differences between x:Bind and Binding.
With x:Bind the root of the binding path is the page the control with the binding is on.
With Binding the root of the binding path is the DataContext of the page the control is on.
Mixing the two can get confusing. If you do need to use a combination of the two then set this.DataContext = this; in the page constructor so they both point to the same thing.
Why are you creating a new List<string> in the getter of the Sites property?
Try to create the source collection only once:
public List<string> Sites { get; } = new List<string>() { "Miami", "Texas" };
I am populating an ItemsControl with various elements, including Buttons and ComboBox elements. Accessing and populating the elements is simple, but I'm stuck on how to detect and associate which Item in the ItemsControl the ComboBox (or Button) belongs to.
To help illustrate the problem, consider the following basic UI:
Now, when I use the ComboBox or Button I want to be able to associate that use only with the ItemControl Item it's a part of. However, currently, if I select an item in the ComboBox every ComboBox in the ItemsControl will reflect that change.
I can capture the SelectedItem in the below ListBox, but ideally, I would like to be able to display both the SelectedItem and which ItemControl Item it came from. For instance, ComboBoxItem1, My First Property - From Item (1).
I am strictly adhering to MVVM principals, and consequently, I am not looking for any solutions using code-behind.
TL;DR
I know the code can become unwieldy. I believe the above description is adequate to state my problem, but I am including the basic boiler plate code below in case it's helpful in posting an answer. (Obviously, I have implemented INotifyProperty and ICommand elsewhere):
MainWindowView.xaml
<ItemsControl Width="300" Height="200" ItemsSource="{Binding MyObservableCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="2" Margin="10">
<StackPanel Margin="0,10,0,10">
<TextBlock Margin="10,0,0,0" Text="{Binding MyProperty}" FontWeight="Bold"/>
<ComboBox Width="270" Text="myBox" ItemsSource="{Binding DataContext.ComboOptions, RelativeSource={RelativeSource AncestorType=ItemsControl}}" DisplayMemberPath="ListItem" SelectedItem="{Binding DataContext.SelectedItem, RelativeSource={RelativeSource AncestorType=Window}}"/>
<RadioButton Width ="270" Content="Button1" Command="{Binding DataContext.GetButtonCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="Button1" Style="{DynamicResource {x:Type ToggleButton}}"/>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
MyComboBoxOptionsViewModel.cs
public class MyComboBoxOptionsViewModel : ObservableObject
{
private MyComboBoxOptionsModel _myComboBoxOptions = new MyComboBoxOptionsModel();
public MyComboBoxOptionsViewModel(MyComboBoxOptionsModel _myComboBoxOptions)
{
this._myComboBoxOptions = _myComboBoxOptions;
}
public string ComboBoxOption
{
get { return _myComboBoxOptions.ComboBoxOption; }
set
{
_myComboBoxOptions.ComboBoxOption = value;
RaisePropertyChangedEvent("ComboBoxOption");
}
}
}
MyComboBoxOptionsModel.cs
public class MyComboBoxOptionsModel
{
public string ComboBoxOption { get; set; }
}
MainWindowViewModel.cs
public class MainWindowViewModel : ObservableObject
{
private ObservableCollection<string> _messages = new ObservableCollection<string>();
private ObservableCollection<MyViewModel> _myObservableCollection = new ObservableCollection<MyViewModel>();
private List<MyComboBoxOptionsViewModel> _comboOptions = new List<MyComboBoxOptionsViewModel>();
private MyComboBoxOptionsViewModel _selectedItem = new MyComboBoxOptionsViewModel(null);
public MainWindowViewModel()
{
_myObservableCollection.Add(new MyViewModel(new MyModel { MyProperty = "My First Property" }));
_myObservableCollection.Add(new MyViewModel(new MyModel { MyProperty = "My Second Property" }));
_comboOptions.Add(new MyComboBoxOptionsViewModel(new MyComboBoxOptionsModel { ComboBoxOption = "Option1" }));
_comboOptions.Add(new MyComboBoxOptionsViewModel(new MyComboBoxOptionsModel { ComboBoxOption = "Option2" }));
_comboOptions.Add(new MyComboBoxOptionsViewModel(new MyComboBoxOptionsModel { ComboBoxOption = "Option3" }));
}
public MyComboBoxOptionsViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
_messages.Add(_selectedItem.ComboBoxOption);
RaisePropertyChangedEvent("SelectedItem");
}
}
public List<MyComboBoxOptionsViewModel> ComboOptions
{
get { return _comboOptions; }
set
{
if (value != _comboOptions)
{
_comboOptions = value;
RaisePropertyChangedEvent("ComboOptions");
}
}
}
public ObservableCollection<MyViewModel> MyObservableCollection
{
get { return _myObservableCollection; }
set
{
if (value != _myObservableCollection)
{
_myObservableCollection = value;
RaisePropertyChangedEvent("MyObservableCollection");
}
}
}
public ObservableCollection<string> Messages
{
get { return _messages; }
set
{
if (value != _messages)
{
_messages = value;
RaisePropertyChangedEvent("Messages");
}
}
}
}
I'm looking at the UI you want and think you basically need a main view model with a collection of item view models.
In that item view model create a command and a selected item property you can bind in your template to the combo box and button. That gives you a strict mvvm binding to a single instance of the combo box value and a command which is executed by the single instance of the button.
Your bindings for combo box items will then need an explicit source as part of the binding so you can hook into one collection of values from the main view model. Or add a collection to your item view model and keep it all nice a clean and together.
As you mention, you're code is very detailed - which is great - but I may have missed some other meaning from it.
Apologies if this is an answer to the wrong question :)
What can I do if I want to bind to some property which is already bound to something else?
In my case, I got a window which has a TextBox. The Text property of this TextBox is data bound to a combo box to it's selectedItem. My Window class got a public string property which I want to update with whatever value is in the TextBox so I wanted to data bind with the Text property but as I said, it's already bound.
How can I update my property with the text in TextBox? Must it use a routed event of TextChanged or can I do it via Xaml?
Also specifically with properties you define them yourself in your window.cs ... how can you bind them to the TextBox.Text? I tried doing it with the <Window> declaration, meaning <Window MyProperty={Binding ...} /> but the property is not recognized there. Why and how do I do it?
You could solve this easily using the MVVM pattern.
ViewModel:
public class ChooseCategoryViewModel : ViewModelBase
{
private string[] _categories =
{ "Fruit", "Meat", "Vegetable", "Cereal" };
public string[] Categories
{
get { return _categories; }
}
private string _selectedCategory;
public string SelectedCategory
{
get { return _selectedCategory; }
set
{
_selectedCategory = value;
OnPropertyChanged("SelectedCategory");
if (value != null && CategoryName != value)
CategoryName = value;
}
}
private string _categoryName;
public string CategoryName
{
get { return _categoryName; }
set
{
_categoryName = value;
OnPropertyChanged("CategoryName");
if (Categories.Contains(value))
{
SelectedCategory = value;
}
else
{
SelectedCategory = null;
}
}
}
}
XAML:
<ComboBox ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}" />
<TextBox Text="{Binding CategoryName}" />
I have one model that implements INotifyPropertyChanged through BaseModel class.
It has other model as element inside of it.
class SIDPoslJavnaUstanova : BaseModel
{
private int? _sid_posl_javna_ustanova_id;
...
private decimal? _udaljenost;
private SIDJavnaUstanova _sid_javna_ustanova;
public SIDJavnaUstanova SidJavnaUstanova
{
get { return _sid_javna_ustanova; }
set {
if (_sid_javna_ustanova != value)
{
_sid_javna_ustanova = value;
if (_sid_javna_ustanova != null)
{
_sid_javna_ustanova_id = _sid_javna_ustanova.SidJavnaUstanovaId;
}
else
{
_sid_javna_ustanova_id = null;
}
RaisePropertyChanged("SidJavnaUstanova");
}
}
}
I have viewmodel that has observable collection of this model objects.
class BaseViewModel<T> : ObservableObject
{
private ObservableCollection<T> _elements = new ObservableCollection<T>();
public ObservableCollection<T> Elements
...
class SIDPoslJavnaUstanovaViewModel : BaseViewModel<SIDPoslJavnaUstanova>
{
}
}
And finally, mainviewmodel that is bound to view:
class MainViewModel : BaseViewModel<Store>
{
private SIDJavnaUstanovaViewModel _sidJavnaUstanovaViewModel;
private SIDJavnaUstanova _sidJavnaUstanova;
public SIDPoslJavnaUstanovaViewModel SidPoslJavnaUstanovaViewModel
{
get { return _sidPoslJavnaUstanovaViewModel; }
set
{
if (_sidPoslJavnaUstanovaViewModel != value)
{
_sidPoslJavnaUstanovaViewModel = value;
RaisePropertyChanged("SidPoslJavnaUstanovaViewModel");
}
}
}
public SIDJavnaUstanovaViewModel SidJavnaUstanovaViewModel
{
get { return _sidJavnaUstanovaViewModel; }
set
{
if (_sidJavnaUstanovaViewModel != value)
{
_sidJavnaUstanovaViewModel = value;
RaisePropertyChanged("SidJavnaUstanovaViewModel");
}
}
}
SidJavnaUstanova is only used to populate combobox, and to bind to object when choosen.
I have combobox in datagrid, that has mulitple lines. Element is SIDJAVNAUSTANOVA , and dropdown is SIDJAVNAUSTANOVAVIEWMODEL.
Dropdown is SIDJAVNAUSTANOVAVIEWMODEL.ELEMENTS
(cannot show you picture not enough reputation)
<src:BaseWindow.Resources>
<viewmod:MainViewModel x:Key="StoreViewM"/>
</src:BaseWindow.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Width="140" Header="{StaticResource name}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=SidJavnaUstanovaViewModel.Elements,
Source={StaticResource StoreViewM}}"
SelectedItem="{Binding Path=SidJavnaUstanova,UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}"
DisplayMemberPath="Naziv"
SelectedValue="{Binding Path=SidJavnaUstanova, Mode=TwoWay}">
</ComboBox>
...
Everything is working fine except when combobox is changed, element SIDJavnaUstanova of object SIDPoslJavnaUstanova is changed, and I can catch this in its model property. But what I must have, is to catch change of this SidJavnaUstanova in viewmodel, so I can implement check-out if there are duplicates of sidjavnaustanova in sidposljavnaustanovaviewmodel.elements. I cannot realize how to do that.
Something like
SIDPoslJavnaUstanova.Elements.??? SIDJavnaUstanova
I cannot do this because elements is observable collection.
Maybe it is a bad model, please suggest something or help with current code.
You need to a) specify source for SelectedItem b) bind SelectedItem to the property of the same type, as elements in your collection (i.e. SIDPoslJavnaUstanova in your case).
This should work, i guess:
<ComboBox ItemsSource="{Binding Path=SidJavnaUstanovaViewModel.Elements,
Source={StaticResource StoreViewM}}"
SelectedItem="{Binding Path=SelectedModel,UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay, Source={StaticResource StoreViewM}}"
DisplayMemberPath="Naziv">
</ComboBox>
.........................................
//MainViewModel
public SIDPoslJavnaUstanova SelectedModel
{
get { return _selectedModel; }
set
{
if (_selectedModel != value)
{
_selectedModel = value;
RaisePropertyChanged("SelectedModel");
}
}
}
And yes, this is some awful design.