Changes to ObservableCollection from UI not updating the Bound Collection - c#

In XAML I have a list view:
<UserControl x:Class="HspSetup.View.UserInputData"
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:HspSetup.View"
xmlns:viewModel="clr-namespace:HspSetup.ViewModel"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:Behaviours="clr-namespace:HspSetup.Behaviours"
mc:Ignorable="d"
Height="Auto" Width="Auto">
<UserControl.Resources>
<viewModel:UserInputDataViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="0.1*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="0.5*" />
</Grid.ColumnDefinitions>
<TextBlock Background="Coral" Height="50"
VerticalAlignment="Center" TextAlignment="Center"
Grid.Row="0" Grid.ColumnSpan="3">
<TextBlock.Text>Some More Data</TextBlock.Text>
<TextBlock.FontFamily>Segoe UI</TextBlock.FontFamily>
<TextBlock.Foreground>White</TextBlock.Foreground>
<TextBlock.FontSize>30</TextBlock.FontSize>
</TextBlock>
<ListView Grid.Row="2" Width="Auto"
Behaviours:GridViewColumnResize.Enabled="True"
Height="Auto" Name="OrderNumberListView"
IsSynchronizedWithCurrentItem="True" Grid.Column="1"
DataContext="{Binding Source={StaticResource ViewModel}}"
ItemsSource="{Binding ConfigObjects}" >
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="Behaviours:LostFocus.LostFocusCommand"
Value="{Binding Path=LostFocusCommand, Source={StaticResource ViewModel}}"/>
<Setter Property="Behaviours:LostFocus.CommandParem"
Value="{Binding}"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Order Name"
Behaviours:GridViewColumnResize.Width="*">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding OrderNumber, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource flatTextBox}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Type Name"
Behaviours:GridViewColumnResize.Width="*">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding TypeName, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource flatTextBox}">
</TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Behaviours:GridViewColumnResize.Width="*" >
<GridViewColumn.HeaderTemplate>
<DataTemplate>
<Button Content="Add"
Command="{Binding AddConfigObjectCommand, Mode=OneWay, Source={StaticResource ViewModel}}"/>
</DataTemplate>
</GridViewColumn.HeaderTemplate>
<GridViewColumnHeader HorizontalContentAlignment="Stretch"/>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Name="RemoveButton"
Content="Remove"
Command="{Binding RemoveConfigObjectCommand, Mode=OneWay, Source={StaticResource ViewModel}}" CommandParameter="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
The Corresponding UI looks like this:
On Click of an Add button I add an Empty row as follows :
I Enter text into the text boxes.
The change made to the listview by entering text in the textbox is not updating the observableCollection to which the textboxes are bound.
The class of the observable collection is as below :
class UserInputDataViewModel: BaseViewModel
{
private ObservableCollection<ConfigObject> _configObjects;
public ObservableCollection<ConfigObject> ConfigObjects
{
get { return _configObjects; }
set
{
_configObjects = value;
OnPropertyChanged("ConfigObjects");
}
}
private ICommand _addConfigObject;
public ICommand AddConfigObjectCommand
{
get
{
if (_addConfigObject == null)
{
_addConfigObject = new RelayCommand(
p => AddConfigObject(),
p => CanAddConfigObject);
}
return _addConfigObject;
}
}
private void AddConfigObject()
{
ConfigObjects.Add(new ConfigObject() { OrderNumber = "", TypeName = "" });
OnPropertyChanged("ConfigObjects");
}
private ICommand _removeConfigObject;
public ICommand RemoveConfigObjectCommand
{
get
{
if (_removeConfigObject == null)
{
_removeConfigObject = new RelayCommand(
p => RemoveConfigObject((ConfigObject)p),
p => CanRemoveConfigObject);
}
return _removeConfigObject;
}
}
private void RemoveConfigObject(ConfigObject configObject)
{
ConfigObjects.Remove(configObject);
OnPropertyChanged("ConfigObjects");
}
bool CanAddConfigObject
{
get { return true; }
}
bool CanRemoveConfigObject
{
get { return true; }
}
}
class BaseViewModel:INotifyPropertyChanged
{
/// <summary>
/// Name of the property that changed
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Send the notification to the property that change
/// </summary>
/// <param name="pPropertyName">name of the property</param>
protected virtual void OnPropertyChanged(string pPropertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this,
new PropertyChangedEventArgs(pPropertyName));
}
}
}
internal class ConfigObject : INotifyPropertyChanged
{
private string _orderNumber;
public string OrderNumber
{
get { return _orderNumber; }
set { _orderNumber = value; OnPropertyChanged("OrderNumber"); }
}
private string _typeName;
public string TypeName
{
get { return _typeName; }
set { _typeName = value; OnPropertyChanged("TypeName"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string paramname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(paramname));
}
}

By editing the text in the textboxes you do not change the ObservableCollection but you change one of the objects inside the collection. The collection is not aware of changing the internal(!) state of one the objects in it. Those changes can be captured by the PropertyChanged event of your ConfigObject class.

Related

Binding to dependencyproperty in user control and calling CanExecute on nested property change

I have created a user control that essentially contains a bunch of comboboxes, called SearchParamsControl. The SearchParamsControl contains all the UI elements required to set everything in a SearchParams class:
My SearchParamsControl:
/// <summary>
/// Interaction logic for SearchParamsControl.xaml
/// </summary>
public partial class SearchParamsControl : INotifyPropertyChanged
{
public SearchParamsControl()
{
InitializeComponent();
}
/// <summary>
/// Radius entries bound to combobox
/// </summary>
public Dictionary<double, string> RadiusEntries
{
get;
set;
} = new Dictionary<double, string>()
{
{0, "This area only" },
{ 0.25, "Within 1/4 mile" },
{ 0.5, "Within 1/2 mile" },
{ 1, "Within 1 mile" },
{ 3, "Within 3 miles" },
{ 5, "Within 5 miles" },
{ 10, "Within 10 miles" },
{ 15, "Within 15 miles" },
{ 20, "Within 20 miles" },
{ 30, "Within 30 miles" },
{ 40, "Within 40 miles" }
};
public Dictionary<PropertyTypeEnum, string> PropertyTypes => PropertyTypeDictionary;
/// <summary>
/// Prices bound to combo box
/// </summary>
public List<int> Prices
{
get;
set;
} = new List<int>()
{
0,
50000,
60000,
70000,
80000,
90000,
100000,
110000,
120000,
125000,
130000,
150000,
200000,
250000,
300000,
325000,
375000,
400000,
425000,
450000,
475000,
500000,
550000,
600000,
650000,
700000,
800000,
900000,
1000000,
1250000,
1500000,
1750000,
2000000,
2500000,
3000000,
4000000,
5000000,
7500000,
10000000,
15000000,
20000000
};
/// <summary>
/// Bedrooms bound to combobox
/// </summary>
public List<int> Bedrooms
{
get;
set;
} = new List<int>()
{
0,
1,
2,
3,
4,
5
};
public StringTrieSet SearchString
{
get
{
return RightMoveCodes.RegionTree;
}
}
public double Radius
{
get => SearchParams.Radius;
set
{
if (SearchParams.Radius != value)
{
SearchParams.Radius = value;
OnSearchParamsChanged();
}
}
}
public int MinBedrooms
{
get { return SearchParams.MinBedrooms; }
set
{
if (SearchParams.MinBedrooms != value)
{
SearchParams.MinBedrooms = value;
OnSearchParamsChanged();
}
}
}
public int MaxBedrooms
{
get { return SearchParams.MaxBedrooms; }
set
{
if (SearchParams.MaxBedrooms != value)
{
SearchParams.MaxBedrooms = value;
OnSearchParamsChanged();
}
}
}
public int MinPrice
{
get { return SearchParams.MinPrice; }
set
{
if (SearchParams.MinPrice != value)
{
SearchParams.MinPrice = value;
OnSearchParamsChanged();
}
}
}
public int MaxPrice
{
get { return SearchParams.MaxPrice; }
set
{
if (SearchParams.MaxPrice != value)
{
SearchParams.MaxPrice = value;
OnSearchParamsChanged();
}
}
}
public SortType SortType
{
get { return SearchParams.Sort; }
set
{
if (SearchParams.Sort != value)
{
SearchParams.Sort = value;
OnSearchParamsChanged();
}
}
}
public SearchParams SearchParams
{
get
{
SearchParams searchParams = (SearchParams)GetValue(SearchParamsProperty);
return searchParams;
}
set
{
SetValue(SearchParamsProperty, value);
}
}
// Using a DependencyProperty as the backing store for MySelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SearchParamsProperty =
DependencyProperty.Register("SearchParams", typeof(SearchParams), typeof(SearchParamsControl), new PropertyMetadata(new SearchParams(), OnSearchParamsPropertyChanged));
public event PropertyChangedEventHandler PropertyChanged;
private static void OnSearchParamsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SearchParamsControl c = d as SearchParamsControl;
if (c != null)
{
c.OnSearchParamsChanged();
}
}
private void OnSearchParamsChanged()
{
OnPropertyChanged(nameof(SearchParams));
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And the xml:
<UserControl x:Class="RightMoveApp.UserControls.SearchParamsControl"
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:RightMoveApp.UserControls"
xmlns:System="clr-namespace:System;assembly=System.Runtime"
xmlns:StyleAlias="clr-namespace:RightMove;assembly=RightMove"
xmlns:viewModel="clr-namespace:RightMoveApp.ViewModel"
xmlns:dataTypes="clr-namespace:RightMove.DataTypes;assembly=RightMove"
xmlns:valueconverters="clr-namespace:RightMoveApp.UserControls.ValueConverters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="uc">
<UserControl.DataContext>
<viewModel:SearchParamsControlViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="dataTypes:SortType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<valueconverters:PropertyTypeConverter x:Key="PropertyTypeConverter" x:Shared="False"/>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboStyle}">
<Setter Property="Margin" Value="0,0,0,1"/>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="Search area"/>
<local:AutoCompleteComboBox Grid.Row="0" Grid.Column="1"
ItemsSource="{Binding ElementName=uc, Path=SearchString}"
SelectedValue="{Binding Path=RegionLocation, Mode=TwoWay}"/>
<Label Grid.Column="0" Grid.Row="1" Content="Search radius"/>
<ComboBox Template="{DynamicResource ComboBoxTemplate1}" Grid.Column="1" Grid.Row="1" Name="comboSearchRadius"
ItemsSource="{Binding ElementName=uc, Path=RadiusEntries}"
SelectedValuePath="Key"
SelectedValue="{Binding ElementName=uc, Path=Radius, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Column="0" Grid.Row="2" Content="Price range (£)"/>
<Grid Grid.Column="1" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Grid.Column="0" Grid.Row="0" Name="comboMinPrice"
ItemsSource="{Binding ElementName=uc, Path=Prices}"
SelectedItem="{Binding ElementName=uc, Path=MinPrice, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Column="1" Grid.Row="0" Content="to"/>
<ComboBox Grid.Column="2" Grid.Row="0" Name="comboMaxPrice"
ItemsSource="{Binding ElementName=uc, Path=Prices}"
SelectedItem="{Binding ElementName=uc, Path=MaxPrice}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<Label Grid.Column="0" Grid.Row="3" Content="No. of bedrooms"/>
<Grid Grid.Column="1" Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Grid.Column="0" Grid.Row="0" Name="comboMinBedrooms"
ItemsSource="{Binding ElementName=uc, Path=Bedrooms}"
SelectedItem="{Binding ElementName=uc, Path=MinBedrooms, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Grid.Column="3" Grid.Row="0" Name="comboMaxBedrooms"
ItemsSource="{Binding ElementName=uc, Path=Bedrooms}"
SelectedItem="{Binding ElementName=uc, Path=MaxBedrooms, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Column="1" Grid.Row="0" Content="to"/>
</Grid>
<Label Grid.Column="0" Grid.Row="4" Content="Sort Type"/>
<ComboBox Grid.Column="1" Grid.Row="4" Name="comboSort"
ItemsSource="{Binding Source={StaticResource dataFromEnum}}"
SelectedItem="{Binding ElementName=uc, Path=SortType, Mode=TwoWay}">
</ComboBox>
</Grid>
I want to bind to the SearchParams dependency property in my MainWindow, which contains the SearchParamsControl:
<GroupBox Grid.Column="0" Grid.Row="0" Header="Search Params" Panel.ZIndex="10">
<controls:SearchParamsControl x:Name="searchControl"
SearchParams="{Binding Path=SearchParams, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding Path=IsSearching, Converter={StaticResource BooleanToReverseConverter}}">
</controls:SearchParamsControl>
</GroupBox>
<Button x:Name="btnSearch" Grid.Column="0" Grid.Row="1"
Content="Search"
IsDefault="True"
Command="{Binding SearchAsyncCommand}"/>
This binding works fine. But, as you can see, I've got a Button in my MainWindow, which is hooked up to a command. I want to know when a property in SearchParams has changed, so that I can called something like SearchCommandAsync.RaiseCanExecuteChanged() so that I can update the state of the Search button. How can I handle this?
Note that I don't want to use the following commented out code (from my Command class), which I think does work, but rather I want to be able to notify that the SearchParams has a changed property and therefore we need to call CanExecute again:
//public event EventHandler CanExecuteChanged
//{
// add
// {
// CommandManager.RequerySuggested += value;
// }
// remove
// {
// CommandManager.RequerySuggested -= value;
// }
//}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
//CommandManager.InvalidateRequerySuggested();
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, new EventArgs());
}
}
Also I don't want to modify SearchParams with a PropertyChanged event handler (imagine it is a closed off class library and I can't modify it).
If I understand your setup correctly, the SearchParams property of the SearchParamsControl on the view should set the data-bound SearchParams source property of the view model whenever the control property changes.
You can then raise the CanExecuteChanged method of the command in the setter of the SearchParams source property.

Background image not being shown unless main window is active

I have an annoying bug that I can't seem to figure out and it is driving me crazy. I have a window with a listbox in it and when an item is selected an new window is opened using mvvm light which displays different details, the problem is when I click on the item in my listbox (BrowseGamesView) the new window that opens (GameDetailsView) does not show the background image unless I click back to the BrowseGamesView. I'm calling to an api and using a converter to build the url for the image. I need the image to show when the new window opens.
GameDetailsView
Where I need the background image to show when the window opens.
<Window.Resources>
<vm:GameDetailsViewModel x:Key="vm"/>
<converters:StringToBackgroundImageConverter x:Key="stringToBackgroundImageConverter"/>
<converters:StringToImageConverter x:Key="stringToImageConverter"/>
</Window.Resources>
<Grid DataContext="{StaticResource vm}">
<Grid.Background>
<ImageBrush ImageSource="{Binding Game.ScreenshotBackground, Converter={StaticResource stringToBackgroundImageConverter}}"/>
</Grid.Background>
</Grid>
BrowseGamesView
Where the listbox is held.
<ListBox ItemsSource="{Binding Games}"
ItemContainerStyle="{DynamicResource ListBoxItemStyle1}"
SelectedItem="{Binding SelectedGame}"
Width="480"
Height="500"
Grid.Row="4">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding cover.image_id, Converter={StaticResource stringToImage}}"
Stretch="Fill"
Width="100"
Height="130"/>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="310"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding name}"
Foreground="White"
FontWeight="Bold"
FontSize="16"
Margin="15 2 0 0"
TextWrapping="Wrap"/>
<TextBlock Text="{Binding aggregated_rating, StringFormat={}{0:F0}, Converter={StaticResource nullRatingConverter}}"
Grid.Column="1"
Foreground="{Binding AggregatedRatingColor}"
FontWeight="Bold"
FontSize="16"
HorizontalAlignment="Center"/>
</Grid>
<ItemsControl Grid.Row="1"
ItemsSource="{Binding DeveloperCompanies}"
Margin="15 2 0 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="commaTextBlock"
Text=", "
Foreground="White"/>
<TextBlock Text="{Binding company.name}"
Foreground="White"
TextWrapping="Wrap"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
<Setter Property="Visibility" TargetName="commaTextBlock" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid Grid.Row="2"
VerticalAlignment="Bottom"
Margin="0 0 0 4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Release Date:"
Foreground="White"
Margin="15 20 0 0"/>
<TextBlock Text="{Binding first_release_date, Converter={StaticResource unixTimestampToDateTimeConverter}}"
Foreground="White"
Margin="10 20 0 0"
Grid.Column="1"/>
</Grid>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
GameDetailsViewModel
public class GameDetailsViewModel : INotifyPropertyChanged
{
private Game game;
public Game Game
{
get { return game; }
set
{
game = value;
OnPropertyChanged("Game");
}
}
public GameDetailsViewModel()
{
Messenger.Default.Register<Game>(this, NotifyMe);
}
public void NotifyMe(Game sentGame)
{
Game = sentGame;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
BrowseGamesViewModel
public class BrowseGamesViewModel : ViewModelBase, INotifyPropertyChanged
{
private string query;
private Game selectedGame;
private int gamesCount;
private Game game;
public RelayCommand ShowGameDetailsViewCommand { private set; get; }
public string Query
{
get { return query; }
set
{
query = value;
OnPropertyChanged("Query");
}
}
public Game Game
{
get { return game; }
set
{
game = value;
OnPropertyChanged("Game");
}
}
public Game SelectedGame
{
get { return selectedGame; }
set
{
selectedGame = value;
OnPropertyChanged("SelectedGame");
GetGameDetails();
ShowGameDetailsViewCommandExecute();
}
}
public ObservableCollection<Game> Games { get; set; }
public int GamesCount
{
get { return gamesCount; }
set
{
gamesCount = value;
OnPropertyChanged("GamesCount");
}
}
public SearchGamesCommand SearchGamesCommand { get; set; }
public BrowseGamesViewModel()
{
Games = new ObservableCollection<Game>();
SearchGamesCommand = new SearchGamesCommand(this);
ShowGameDetailsViewCommand = new RelayCommand(ShowGameDetailsViewCommandExecute);
}
public void ShowGameDetailsViewCommandExecute()
{
Messenger.Default.Send(new NotificationMessage("ShowGameDetailsView"));
}
private async void GetGameDetails()
{
Game = await IGDBHelper.GetGameInformation(SelectedGame.id);
Messenger.Default.Send(Game);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
BrowseGamesView Code Behind
public partial class BrowseGamesView : Window
{
public BrowseGamesView()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
}
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "ShowGameDetailsView")
{
var view = new GameDetailsView();
view.Show();
}
}
}

How to Fill data to data grid in Button command wpf?

I am developing a application following WPF MVVM pattern, There I need to fill a datagrid with data when button click event. So My view model related to grid also get filled with data but it doesn't appear in the grid in screen. What I have done wrong here? Any advice will be much appreciated.
Author-Model Class
public class Author
{
private int id;
private string name;
private string bookTitle;
private bool isMVP;
public Author(int ID, string Name, string BookTitle, bool IsMVP)
{
this.id = ID;
this.name = Name;
this.bookTitle = BookTitle;
this.isMVP = IsMVP;
}
public int ID
{
get { return id; }
set { id = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public string BookTitle
{
get { return bookTitle; }
set { bookTitle = value; }
}
public bool IsMVP
{
get { return isMVP; }
set { isMVP = value; }
}
}
AuthorViewModel
public class AuthorViewModel: ObservableCollection<Author>
{
public AuthorViewModel():base()
{
Add(new Author
(
1,
"Cather",
"Graphics Programming",
true
));
Add(new Author
(
2,
"Mathew Cochran",
"LINQ in Vista",
true
));
Add(new Author
(
3,
"Mike Gold",
"Programming in Vista",
true
));
}
}
MainWindowViewModel
public class MainWindowViewModel
{
public ICommand LoadGridCommand { get; set; }
public MainWindowViewModel()
{
LoadGridCommand = new RelayCommand(LoadGrid,null);
}
private void LoadGrid(object parameter)
{
AuthorViewModel authorVM = new AuthorViewModel();
}
}
MainWindow.xaml
<Window x:Class="CustomCtrldemo.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:CustomCtrldemo"
xmlns:vm="clr-namespace:CustomCtrldemo.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<vm:MainWindowViewModel x:Key="MainWindowVM"/>
<vm:AuthorViewModel x:Key="AuthorVM"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource MainWindowVM }}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox Name="TxtFilterGrid" Grid.Row="0" Grid.Column="0" Width="100" Height="20" />
<Button Grid.Row="1" Grid.Column="0" Content="Load Grid" Width="100" Height="20" Command="{Binding LoadGridCommand}" />
<Grid x:Name="gridAuthors" Grid.Column="1" Grid.RowSpan="4" DataContext="{Binding Source={StaticResource AuthorVM} }" >
<DataGrid x:Name="dgAuthors" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Path=ID}" />
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" />
<DataGridTextColumn Header="Book Title" Binding="{Binding Path=BookTitle}" />
<DataGridCheckBoxColumn Header="MVP Enabled" Binding="{Binding Path=IsMVP}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
<Button Grid.Row="2" Grid.Column="0" Content="Load Control" Width="100" Height="20"/>
<Button Grid.Row="3" Grid.Column="0" Content="Set Content" Width="100" Height="20"/>
<TextBox Name="txtbox1" Grid.Row="4" Grid.Column="0" Width="100" Height="20" />
<TextBox Name="txtUniqueId" Grid.Row="4" Grid.Column="1" Width="100" Height="20"/>
<local:LoadControl />
<WrapPanel HorizontalAlignment="center" Grid.Row="6" Grid.Column="1" >
<Button Content="OK" Width="100" Height="20"/>
<Button Content="Cancel" Width="100" Height="20"/>
</WrapPanel>
</Grid>
</Window>
Your separate AuthorViewModel doesn't make sense. You should instead have a Authors property in the main view model:
public class MainWindowViewModel
{
public ICommand LoadGridCommand { get; }
public ObservableCollection<Author> Authors { get; }
= new ObservableCollection<Author>();
public MainWindowViewModel()
{
LoadGridCommand = new RelayCommand(LoadGrid, null);
}
private void LoadGrid(object parameter)
{
// Authors.Clear();
// Authors.Add(...);
}
}
You should assign an instance of the main view model to the window's DataContext and bind like this:
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<DataGrid ItemsSource="{Binding Authors}" ...>

How to update datagrid dynamically using MVVM in my scenario

I am practicing MVVM application and i am beginner, what i am trying to do is, I have 3 rows in a grid, the first row will contain 3 labels and 3 corresponding buttons. And the second row will contain a button to save the data entered in that textboxes in first row. The third row will contain a datagrid with the same number and type of textboxes (three).
Look can be seen here http://prntscr.com/9v2336
The user will enter the data in first row and then he will press save button in second row and then he must find the written information in the corresponding datagrid columns.
My try is here (Entire Code):
View:
<Window x:Class="WpfApplication4.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.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding TextName}" Height="20" Width="80" HorizontalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding RollNumber}" Height="20" Width="80"></TextBox>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Class}" Height="20" Width="80"></TextBox>
<Label Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center">Name</Label>
<Label Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">RollNumber</Label>
<Label Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center">Class</Label>
</Grid>
<Grid Grid.Row="1" >
<Button Width="80" Height="20" Command="{Binding saveStudentRecord}"> Save</Button>
</Grid>
<Grid Grid.Row="2">
<DataGrid ItemsSource="{Binding DGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding dgName}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Rollnumber" Binding="{Binding dgRollnumber}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Class" Binding="{Binding dgClass}" Width="150"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</Window>
Model:
class Model
{
private string textName;
public string TextName
{
get { return textName; }
}
private string rollNumber;
public string RollNumber
{
get { return rollNumber; }
}
private string cclass;
public string Class
{
get { return cclass; }
}
}
ViewModel:
class ViewModel
{
public bool canExecute { get; set; }
private RelayCommand saveStudentRecord;
private ObservableCollection<Model> dGrid;
public ViewModel()
{
}
private void MyAction()
{
//What to do here to pass all that data to the datagrid corresponding columns
}
}
Where i have problem ?
I have designed the entire body but i am not able to find the logic that how would i assign the entered data in text boxes to corresponding data-grid columns on button click event and binding themusing only MVVM.
Should be simple as adding a new Model to your ObservableCollection<Model> DGrid:
class ViewModel
{
public bool canExecute { get; set; }
private RelayCommand saveStudentRecord;
private ObservableCollection<Model> dGrid;
public ViewModel()
{
dGrid = new ObservableCollection<Model>();
}
private void MyAction()
{
dGrid.Add(new Model(){
TextName = valueOfTextTextBox,
RollNumber = valueOfRollNumberTextBox,
Class = valueOfClassTextBox
});
}
}
Thing to remember here:
dGrid should be a public property, so you can use Databinding with it, e.g.:
public ObservableCollection<Model> dGrid {
get;
private set;
}
EDIT based on the question in commments:
You need to bind the text property of TextBoxes to a property on you ViewModel. As ModelClass holds that kind of info, I would do:
class ViewModel
{
public bool canExecute { get; set; }
private RelayCommand saveStudentRecord;
private ObservableCollection<Model> dGrid;
public ViewModel()
{
dGrid = new ObservableCollection<Model>();
}
public Model EditedModel {
get {
return _editedModel;
}
set {
_editedModel = value;
SignalPropertyChanged("EditedModel");
}
}
private void MyAction()
{
dGrid.Add(EditedModel);
EditedModel = new Model();
}
void SignalPropertyChanged(string propertyName){
if(propertyChanged !=null){
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Of course now your ViewModel and Model need to implement INotifyPropertyChanged interface to notify view of the changes
class Model : INotifyPropertyChanged
{
private string textName;
public string TextName
{
get { return textName; }
set {
textName = value;
SignalPropertyChanged("TextName");
}
}
private string rollNumber;
public string RollNumber
{
get { return rollNumber; }
set {
rollNumber= value;
SignalPropertyChanged("RollNumber");
}
}
private string cclass;
public string Class
{
get { return cclass; }
set {
cclass= value;
SignalPropertyChanged("Class");
}
}
public event PropertyChangedEventHandler propertyChanged;
void SignalPropertyChanged(string propertyName){
if(propertyChanged !=null){
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
EDIT2 - forgot to add XAML part :) You need to bind the textboxes to a new property
<Window x:Class="WpfApplication4.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.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding EditedModel.TextName}" Height="20" Width="80" HorizontalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding EditedModel.RollNumber}" Height="20" Width="80"></TextBox>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding EditedModel.Class}" Height="20" Width="80"></TextBox>
<Label Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center">Name</Label>
<Label Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">RollNumber</Label>
<Label Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center">Class</Label>
</Grid>
<Grid Grid.Row="1" >
<Button Width="80" Height="20" Command="{Binding saveStudentRecord}"> Save</Button>
</Grid>
<Grid Grid.Row="2">
<DataGrid ItemsSource="{Binding DGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding dgName}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Rollnumber" Binding="{Binding dgRollnumber}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Class" Binding="{Binding dgClass}" Width="150"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</Window>

WPF Data Binding not showing

So I'm having what should be a simple XAML Data Binding error. I've got the below XAML with two classes (so far) that they used for DataBinding, a Row, which contains an ObservableCollection rows. The nodes have a bunch of extra associated information and I'm trying to show these nodes in a grid-like fashion (it's going to be used for a path-finding algorithm.)
The problem is that the "Here" TextBlock doesn't show up. But I know that the Nodes are getting bound properly because their values show up in the Debugging StackPanel.
<Window.Resources>
<local:Row x:Key="TestRow">
<local:Node x="0" y="0" Cost="20" />
<local:Node x="0" y="1" Cost="20" />
<local:Node x="0" y="2" Cost="20" />
</local:Row>
</Window.Resources>
<Grid Name="MainGrid" Margin="10,10,10,10" >
<Grid.RowDefinitions>
<RowDefinition Height="150" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Margin="0,25,0,0"
DataContext="{Binding ElementName=AStarListView,
Path=SelectedItem}"
x:Name="Debugging" Orientation="Vertical" >
<TextBlock Text="{Binding x}" />
<TextBlock Text="{Binding y}" />
<TextBlock Text="{Binding Cost}" />
</StackPanel>
<ListView Grid.RowSpan="2" Margin="2,2,2,2" Grid.Column="1"
x:Name="AStarListView"
ItemsSource="{StaticResource TestRow}" >
<ListView.ItemTemplate>
<DataTemplate DataType="local:Row">
<ListView ItemsSource="{Binding nodes}" Width="48" Height="48" >
<ListView.ItemTemplate>
<DataTemplate DataType="local:Node">
<TextBlock Text="Here" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here's the (stripped) Node class.
public class Node : INotifyPropertyChanged
{
public Tuple<int, int> coordinates { get; set; }
public int x
{
get
{
if (this.coordinates == null)
return -1;
return this.coordinates.Item1;
}
set
{
if (this.coordinates != null)
this.coordinates = new Tuple<int, int>(value, y);
else
this.coordinates = new Tuple<int, int>(value, -1);
OnPropertyChanged("x");
}
}
public int y
{
get
{
if (this.coordinates == null)
return -1;
return this.coordinates.Item2;
}
set
{
if (this.coordinates != null)
this.coordinates = new Tuple<int, int>(x, value);
else
this.coordinates = new Tuple<int, int>(-1, value);
OnPropertyChanged("y");
}
}
private Node _parent;
private int _Cost;
public int Cost
{
get
{
return _Cost;
}
set
{
_Cost = value;
OnPropertyChanged("Cost");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs args =
new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, args);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Here's the Row class:
public class Row : ObservableCollection<Node>
{
public ObservableCollection<Node> nodes { get; set; }
public Row()
{
this.nodes = new ObservableCollection<Node>();
}
}
Here's the corrected XAML, the class definitions are correct in the question, need to replace "AStarListView" with this XAML.
<ListView Grid.RowSpan="2" Margin="2,2,2,2" Grid.Column="1" x:Name="AStarListView"
ItemsSource="{StaticResource TestRow}" >
<ListView.ItemTemplate>
<DataTemplate DataType="local:Node">
<Grid Background="#dddddd" >
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding x}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding y}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Cost}"/>
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="24"/>
</Grid.ColumnDefinitions>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My problem was that I had the ListViews too deeply nested. The inner ListView was binding to the "nodes" property of a Node, which didn't exist.

Categories