Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I'm just starting out with WPF and MVVM framework. I have a Window with two DataGrids and I would like to load data in one based on the row selection of the other.
Has anyone got any advice or examples ,I've tried numerous ways but none seem to work out.
Thank you
Look I can help you a little bit, you maybe need to monitor the selected item (either with binding or an event trigger). When it changes to use the new item to fetch the needed info from your data and then re-populate the source collection for the second data grid.
Here is a sample code it can help you:
Xaml
<DataGrid SelectedValue="{Binding Path=SelectedValue}"
ItemSource="{Binding Path=Source1}"/>
<DataGrid ItemSource="{Binding Path=Source2}"/>
Code Behind
public ObservableCollection Source1 { get; private set; }
public ObservableCollection<data> Source2 { get; private set; }
public Data SelectedValue
{
get { return _selectedValue; }
set
{
if (_selectedValue == value) return;
_selectedValue = value;
PopulateSource2();
}
}
private void PopulateSource2()
{
Source2.Clear();
//Get your other data from DB here
Source2.Add(SelectedValue);//This is just to show that it works
}
I am posting a simple code. You can change it as per your needs
View
<Window x:Class="MultipleDataGrid.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">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Column="0" ItemsSource="{Binding SourceOne}" SelectedItem="{Binding SelectedItem}" />
<DataGrid Grid.Column="1" ItemsSource="{Binding SourceTwo}" />
</Grid>
</Window>
View Code Behind
using System.Windows;
namespace MultipleDataGrid
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
}
View Model
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
namespace MultipleDataGrid
{
class ViewModel : INotifyPropertyChanged
{
private readonly object _lockOne = new object();
private readonly object _lockTwo = new object();
private ObservableCollection<StringValue> _sourceOne;
public ObservableCollection<StringValue> SourceOne
{ get { return _sourceOne; } }
private Dictionary<string, List<StringValue>> _sourceTwoList;
private List<StringValue> _sourceTwo;
public List<StringValue> SourceTwo
{
get { return _sourceTwo; }
set { _sourceTwo = value; RaisePropertyChanged("SourceTwo"); }
}
private StringValue _selectedItem;
public StringValue SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
PopulateDataGridTwo(value.Value);
RaisePropertyChanged("SelectedItem");
}
}
private void PopulateDataGridTwo(string key)
{
if (_sourceTwoList.ContainsKey(key))
{
SourceTwo = _sourceTwoList[key];
}
}
public ViewModel()
{
_sourceOne = new ObservableCollection<StringValue>
{
new StringValue("Key1"),new StringValue("Key2"),new StringValue("Key3")
};
_sourceTwoList = new Dictionary<string, List<StringValue>>();
BindingOperations.EnableCollectionSynchronization(_sourceOne, _lockOne);
BindingOperations.EnableCollectionSynchronization(_sourceTwoList, _lockTwo);
_sourceTwoList.Add("Key1", new List<StringValue> { new StringValue("KVOneOne"),new StringValue("KVOneTwo") });
_sourceTwoList.Add("Key2", new List<StringValue> { new StringValue("KVTwoOne"),new StringValue("KVTwoTwo") });
_sourceTwoList.Add("Key3", new List<StringValue> { new StringValue("KVThreeOne"),new StringValue("KVThreeTwo") });
RaisePropertyChanged("SourceOne");
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propName)
{
var pc = PropertyChanged;
if (pc != null)
pc(this, new PropertyChangedEventArgs(propName));
}
}
public class StringValue
{
public StringValue(string s)
{
_value = s;
}
public string Value { get { return _value; } set { _value = value; } }
string _value;
}
}
I have used the code from here to display the string in the DataGrid.
I hope the solution helps.
Here's a crude but working example that I decided to type up in between Battlefield rounds...
XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication3"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Vm />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DataGrid x:Name="Selector" ItemsSource="{Binding Source}" />
<DataGrid Grid.Column="1" ItemsSource="{Binding SelectedItem, ElementName=Selector}" />
</Grid>
</Window>
Code:
namespace WpfApplication3
{
public class Vm
{
public ObservableCollection<ObserverableGrouping> Source { get; set; }
public Vm()
{
Source = new ObservableCollection<ObserverableGrouping>() {
new ObserverableGrouping("Group1"){ new ObjectModel() { Name = "A", Description = "Group1 Object1" }, new ObjectModel() { Name = "B", Description = "Group1 Object2" } },
new ObserverableGrouping("Group2"){ new ObjectModel() { Name = "C", Description = "Group2 Object1" }, new ObjectModel() { Name = "D", Description = "Group2 Object2" } }
};
}
}
public class ObserverableGrouping : ObservableCollection<ObjectModel>
{
public string GroupDescription { get; set; }
public ObserverableGrouping(string Name)
{
this.GroupDescription = Name;
}
}
public class ObjectModel
{
public string Name {get;set;}
public string Description {get;set;}
}
}
Hope this helps.
Related
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.
WPF C# How can I filter a ObservableCollection or QueryableCollectionView?
I want to filter and list all categories which has the same duplicated Category Code.
Like code: Test123 consists of these categories. In other word, the categorycode should be unique.
I tried this with QueryableCollectionView:
var test = CategoryCollection.GroupBy(Category => Category.Code).Where(w => w.Count() > 1);
But it didn't work.
My code sample:
public ObservableCollection<Category> GetCategory
{
get
{
this._getCategory = GetCategory();
this._getCategory.GroupBy(category => category.Code).ToList().Where(w => w.Count() > 1); ;
this._getCategory = new ObservableCollection<Category>(_getCategory);
return this._getCategory.
}
set
{
if (this._getCategory != value)
{
this._getCategory = value;
this.OnPropertyChanged("GetCategory");
}
}
}
I've made a code sample that works as you've described now, see below:
MainWindow.xaml
<Window x:Class="TestWpfApplication.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" Name="CategoryListBox" ItemsSource="{Binding Path=CategoryCollection}" DisplayMemberPath="Description" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding ElementName=CategoryListBox, Path=SelectedItem.Code}" />
</Grid>
MainWindow.xaml.cs
using System.Windows;
namespace TestWpfApplication
{
public partial class MainWindowView : Window
{
private readonly MainWindowViewModel _mainWindowViewModel = new MainWindowViewModel();
public MainWindowView()
{
InitializeComponent();
DataContext = _mainWindowViewModel;
}
}
}
MainWindowViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
namespace TestWpfApplication
{
internal class MainWindowViewModel
{
private readonly List<Category> _categories;
public MainWindowViewModel()
{
_categories = new List<Category>
{
new Category {Code = 1, Description = "Blah"},
new Category {Code = 1, Description = "Blah"},
new Category {Code = 2, Description = "Pop"},
new Category {Code = 3, Description = "No"},
new Category {Code = 3, Description = "No"},
new Category {Code = 4, Description = "Yes"}
};
HookUpCategoryEvents();
CategoryCollection = CollectionViewSource.GetDefaultView(_categories);
CategoryCollection.Filter = OnlyShowIfMoreThanOne;
}
private bool OnlyShowIfMoreThanOne(object obj)
{
Category item = obj as Category;
return _categories.Count(c => c.Code == item.Code) > 1;
}
public ICollectionView CategoryCollection { get; }
private void HookUpCategoryEvents()
{
// If you add items or remove them at any point then you need to call this method
// It removes the event so you don't get existing items being 'hooked up' double or more times
foreach (var category in _categories)
{
category.CodeChanged -= CategoryOnCodeChanged;
category.CodeChanged += CategoryOnCodeChanged;
}
}
private void CategoryOnCodeChanged(object sender, EventArgs e)
{
CategoryCollection.Refresh();
}
}
}
Category.cs
using System;
namespace TestWpfApplication
{
public class Category
{
private int _code;
private string _description;
public event EventHandler CodeChanged;
public int Code
{
get => _code;
set
{
if (_code != value)
{
_code = value;
OnCodeChanged();
}
}
}
public string Description
{
get => _description;
set => _description = value;
}
private void OnCodeChanged()
{
CodeChanged?.Invoke(this, EventArgs.Empty);
}
}
}
This displays a simple ListBox that is set to show the Description property of the Category class and a TextBox that shows the SelectedItem.Code of the ListBox.
In the ViewModel the ICollectionView has a filter applied to it so that it only shows items those codes appear more than once. An event is fired from the Category class upon setting the Code property (from the TextBox) which the MainWindowViewModel listens to and uses to call .Refresh() on the ICollectionView. There is no validation or other things that you'd want in production code but this should show you the basics of what you're wanting.
var test = CategoryCollection.GroupBy(Category => Category.Code).toList().Where(w => w.Count() > 1);
List<Category> listResult= yourObservableCollection.GroupBy(c => c.Code)
.Select(cl=>new Caegory
{
Code=cl.First().Code;
// other parameters of Category here
}).ToList().Where(w=>w.Count()>1);
ObservableCollection<Category> result=new ObservableCollection<Category>(listResult);
I don't know about QueryableCollectionView, but for ObservableCollection it works
Recently I started converting a proof of concept UWP app to a working WPF app.
What I want is to have two dropdowns (combobox) of "characters" I can choose, I want them databound to an ObservableCollection property, where the characters I selected is stored in a different Character property for player 1 then player 2.
I had databinding on dropdowns working in the UWP app, but I can't get it to work in the WPF app.
In the WPF app, the comboboxes stay empty and I can't select an option.
I tried following the answer to this question, but I think I'm missing something: Binding a WPF ComboBox to a custom list
Here is the code, kept minimal:
Character.cs
public class Character : INotifyPropertyChanged
{
private int _id;
public int Id
{
get
{
return _id;
}
set
{
_id = value;
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public Character(int id, string name)
{
Id = id;
Name = name;
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml
<Window x:Class="SmashWiiUOverlayManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SmashWiiUOverlayManager"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0">
<ComboBox
Name="Player1CharacterDropdown"
ItemsSource="{Binding CharacterList, Mode=TwoWay}"
SelectedItem="{Binding Player1SelectedCharacter, Mode=TwoWay}"
SelectedValuePath="Name"
DisplayMemberPath="Name"
Width="144">
</ComboBox>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right">
<ComboBox
Name="Player2CharacterDropdown"
ItemsSource="{Binding CharacterList, Mode=TwoWay}"
SelectedItem="{Binding Player2SelectedCharacter, Mode=TwoWay}"
SelectedValuePath="Character"
DisplayMemberPath="Name"
Width="144">
</ComboBox>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Character> _characterList;
public ObservableCollection<Character> CharacterList
{
get
{
return _characterList;
}
set
{
_characterList = value;
}
}
private Character _player1SelectedCharacter;
public Character Player1SelectedCharacter
{
get
{
return _player1SelectedCharacter;
}
set
{
_player1SelectedCharacter = value;
}
}
private Character _player2SelectedCharacter;
public Character Player2SelectedCharacter
{
get
{
return _player2SelectedCharacter;
}
set
{
_player2SelectedCharacter = value;
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
CharacterList = new ObservableCollection<Character>
{
new Character(0, "Mario"),
new Character(1, "Luigi"),
new Character(2, "Wario"),
};
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
This code currently leaves the comboboxes empty.
When I use:
Player1CharacterDropdown.ItemsSource = new ObservableCollection<Character>
{
new Character(0, "Mario", ".\\images\\mario.png"),
new Character(1, "Luigi", ".\\images\\luigi.png"),
new Character(2, "Wario", ".\\images\\wario.png"),
};
... the combox gets filled, but it's databound to the property, which is what I would like.
What am I missing here?
I'm creating a Xamarin.Forms application with a list. The itemSource is a reactiveList. Adding new items to the list however does not update the UI. What is the correct way of doing this?
List Definition
_listView = new ListView();
var cell = new DataTemplate(typeof(TextCell));
cell.SetBinding(TextCell.TextProperty, "name");
cell.SetBinding(TextCell.DetailProperty, "location");
_listView.ItemTemplate = cell;
Binding
this.OneWayBind(ViewModel, x => x.monkeys, x => x._listView.ItemsSource);
this.OneWayBind(ViewModel, x => x.save, x => x._button.Command); //save adds new items
View Model
public class MyPageModel : ReactiveObject
{
public MyPageModel()
{
var canSave = this.WhenAny(x => x.username, x => !String.IsNullOrWhiteSpace(x.Value) && x.Value.Length>5);
save = ReactiveCommand.CreateAsyncTask(canSave, async _ =>
{
var monkey = new Monkey { name = username, location = "# " + DateTime.Now.Ticks.ToString("X"), details = "More here" };
monkeys.Add(monkey);
username = "";
});
monkeys = new ReactiveList<Monkey>{
new Monkey { name="Baboon", location="Africa & Asia", details = "Baboons are Africian and Arabian Old World..." }
};
_monkeys.ChangeTrackingEnabled = true;
}
private string _username = "";
public string username
{
get { return _username; }
set { this.RaiseAndSetIfChanged(ref _username, value); }
}
private double _value = 0;
public double value
{
get { return _value; }
set { this.RaiseAndSetIfChanged(ref _value, value); }
}
public ReactiveCommand<Unit> save { get; set; }
public ReactiveList<Monkey> _monkeys;
public ReactiveList<Monkey> monkeys
{
get { return _monkeys; }
set { this.RaiseAndSetIfChanged(ref _monkeys, value); }
}
}
public class Monkey
{
public string name { get; set; }
public string location { get; set; }
public string details { get; set; }
}
Tried it with the ReactiveList property being a normal auto-property as well as the one in the code above where I used the RaiseAndSetIfChanged method.
Your problem is that you are changing monkeys on a non-UI thread. In other frameworks, this would throw an Exception, but in AppKit / UIKit this just Does Weird Stuff (usually nothing).
save = ReactiveCommand.Create(canSave);
save.Subscribe(_ =>
{
// Do the part that modifies UI elements (or things bound to them)
// in the RxCmd's Subscribe. This is guaranteed to run on the UI thread
var monkey = new Monkey { name = username, location = "# " + DateTime.Now.Ticks.ToString("X"), details = "More here" };
monkeys.Add(monkey);
username = "";
});
This technique seems awfully complicated. It makes me think that you want to do something more complicated that I think it to be.
Here's a clean solutionfor adding an object to a list box by binding to a ReactiveList<T> in pure MVVM fashion.
First, the xaml
<Window x:Class="ReactiveUIListBox.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:ReactiveUIListBox.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<viewModel:MainWindowViewModel x:Key="MainWindowViewModel"/>
</Window.Resources>
<Window.DataContext>
<StaticResource ResourceKey="MainWindowViewModel"/>
</Window.DataContext>
<Grid DataContext="{StaticResource MainWindowViewModel}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Background="Azure">
<ListBox ItemsSource="{Binding Model.TestReactiveList}"></ListBox>
</Grid>
<Grid Grid.Column="1">
<Button x:Name="button" Command="{Binding TestCommand}" Content="Button" HorizontalAlignment="Left" Margin="78,89,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Grid>
</Window>
Here's the ViewModel
using System.Windows.Input;
using ReactiveUIListBox.Model;
using SecretSauce.Mvvm.ViewModelBase;
namespace ReactiveUIListBox.ViewModel
{
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
Model = new ReactiveModel<string>();
}
public ReactiveModel<string> Model
{
get;
set;
}
public ICommand TestCommand
{
get { return new RelayCommand(ExecuteTestCommand); }
}
private void ExecuteTestCommand(object obj)
{
Model.TestReactiveList.Add("test string");
}
}
}
and finally, here's the Model.
using ReactiveUI;
namespace ReactiveUIListBox.Model
{
public class ReactiveModel<T> : ReactiveObject
{
public ReactiveModel()
{
TestReactiveList= new ReactiveList<T>();
}
public ReactiveList<T> TestReactiveList
{
get;
set;
}
}
}
Pressing the button will populate the ListBox. Hopefully I have not completely over simplified what you are trying to do, but your code does seem to be all over the place.
I can't differentiate between your code that fits in the View, Model or ViewModel.
Cheers.
I've got some bindings in UI:
<Window x:Class="Tester.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="377" Width="562" xmlns:my="clr-namespace:MyApp">
<Grid>
<TextBlock Text="{Binding Path=current.Text}" Name="Text1" />
<TextBlock Text="{Binding Path=current.o.Text}" Name="Text2" />
</Grid>
</Window>
Code:
class Coordinator : INotifyPropertyChanged{
List<Myclass1> list;
int currId = 0;
public Myclass1 current{
return list[currId];
}
public int CurrId
{
get { return currId; }
set
{
currId = value;
this.PropertyChanged(this,new PropertyChangedEventArgs("current"));
}
}
class Myclass1{
public string Text{get;}
public Myclass2 o{get;}
}
class Myclass2{
public string Text{get;}
}
When currId changes Tex1 in UI changes too,but Text2 doesn't.
I'm assuming this happens because Text2's source isn't updated.
Does anyone know how to fix it?
I'm not sure if this is what you are looking for, but for something similar I used the PropertyObserver class from Josh Smith's MVVM framework.
It should work assuming you implement your classes properly. With some fixes my code looks like this:
<Window x:Class="WpfBindingTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<StackPanel>
<TextBlock Text="Enter 0, 1 or 2 below and press Tab:"/>
<TextBox Text="{Binding CurrId}"/>
<Button/> <!--Button here is just for another tab stop-->
<TextBlock Text="{Binding Path=current.Text}" Name="Text1" />
<TextBlock Text="{Binding Path=current.o.Text}" Name="Text2" />
</StackPanel>
</Grid>
</Window>
it works fine with classes implemented like this:
public partial class Window1: Window {
public Window1() {
InitializeComponent();
DataContext = new Coordinator();
}
}
class Coordinator: INotifyPropertyChanged {
List<Myclass1> list;
int currId = 0;
public Myclass1 current {
get { return list[currId]; }
}
public int CurrId {
get { return currId; }
set {
currId = value;
this.PropertyChanged(this, new PropertyChangedEventArgs("current"));
}
}
public Coordinator() {
list = new List<Myclass1>(){
new Myclass1(){ Text = "1", o = new Myclass2(){Text="1.1"}},
new Myclass1(){ Text = "2", o = new Myclass2(){Text="2.2"}},
new Myclass1(){ Text = "3", o = new Myclass2(){Text="3.3"}}
};
}
public event PropertyChangedEventHandler PropertyChanged;
}
class Myclass1 {
public string Text { get; set; }
public Myclass2 o { get; set; }
}
class Myclass2 {
public string Text { get; set; }
}
Maybe you need to implement INotifyPropertyChanged in Myclass2 also.