How to have a custom display in ListBox with DataTemplate? - c#

I need to display cards in a ListBox in a specific layout:
https://imgur.com/a/0U8eqTc
I've tried to find a way to use 2 types of DataTemplate but I have no idea how to do it. I decided to make a template which contains 6 card Template (like this):
https://imgur.com/VrOlYcR
Here's what my current Template looks like:
<ControlTemplate x:Key = "CardTemplate" TargetType = "Button">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="4*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="{Binding Path=Image}"/>
<TextBlock Grid.Row="1" Text="{Binding Path=Titre}"/>
</Grid>
</ControlTemplate>
<DataTemplate x:Key="DataTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Grid.Row="0" Template="{StaticResource CardTemplate}"/>
<Grid Grid.Column="1" Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Template="{StaticResource CardTemplate}"/>
<Button Grid.Row="1" Template="{StaticResource CardTemplate}"/>
</Grid>
<Grid Grid.Column="0" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Template="{StaticResource CardTemplate}"/>
<Button Grid.Row="1" Template="{StaticResource CardTemplate}"/>
</Grid>
<Button Grid.Column="1" Grid.Row="1" Template="{StaticResource CardTemplate}"/>
</Grid>
</DataTemplate>
Which I intend to display in a ListBox:
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Name="ListBox" ItemTemplate="{DynamicResource DataTemplate}"
ScrollBar.Scroll="ScrollOnBottom">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Here's how my cod behind basically works:
class Cards
{
public List<Card> cards; // list of 6 Card objects
}
class Card
{
string title;
BitmapImage image;
public string Title { get => title; set => title = value; }
public BitmapImage Image { get => image; set => image = value; }
}
ObservableCollection<Cards> cc = new ObservableCollection<Cards>();
/*
Cards are already filled with 6 Card
cc filled with Cards
*/
formationListBox.ItemsSource = cc;
Here's the problem, it displays the right amount of Cards but the buttons are empty. I don't know how to bind a specific object to each button.

To Give an example of what Sinatr commented. You should approach this from an Mvvm perspective. first you should add a View Model for the View that this Window is in. This will contain a list of objects that are DisplayCards each object will store the string and image.
public class DisplayCard : INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set
{
if (value != _title) { _title = value; RaisePropertyChanged(); }
}
}
private string _cardImage;
public string CardImage
{
get { return _cardImage; }
set
{
if (value != _cardImage) { _cardImage = value; RaisePropertyChanged(); }
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class YourViewVM : INotifyPropertyChanged
{
private ObservableCollection<DisplayCard> _cardCollection;
public ObservableCollection<DisplayCard> CardCollection
{
get { return _cardCollection; }
set
{
if (value != _cardCollection) { _cardCollection = value; RaisePropertyChanged(); }
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then you need to make the list CardCollection set to the ListBox's ItemSource. Then use a datatemplate to bind the DisplayCards properties to the containing object.
<ListBox Name="lbTodoList" HorizontalContentAlignment="Stretch" ItemSource="{Binding CardCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="4*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="{Binding Image}"/>
<TextBlock Grid.Row="1" Text="{Binding Title}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You need to make sure you set the YourViewVM As the DataContext of the View. A simple search should solve how to do that.
The above should be enough to allow you to refactor your code such that it works.

Related

How can I hide rows in a DataGrid?

I am trying to create a "notes" app using wpf mvvm. I have a MainWindow containing a DataGrid with data that is bound to an ObservableCollection. In MainWindowView I have a "Find" button which calls FindWindowDialog. In the FindWindowDialog in the textbox, I must enter the text that will be searched and click "Find", after which the DataGrid MainWindowView should hide those lines whose content does not contain the searched text. I don't really know how to do this, after 2 days of searching I decided to ask a question. I googled on this topic and I have a suggestion that I should delve into the messenger pattern and converters
MainWindow.xaml(View)
<Window x:Class="NotesARK6.View.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:NotesARK6.View"
xmlns:model="clr-namespace:NotesARK6.Model"
xmlns:viewmodel="clr-namespace:NotesARK6.ViewModel"
mc:Ignorable="d"
Title="Notes" Height="450" Width="800"
x:Name="_mainWindow"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding NotesCollection}" SelectedItem="{Binding SelectedNote}" IsReadOnly="True" AutoGenerateColumns="False" x:Name="DataGrid_Notes" Margin="5" Grid.Row="2" Grid.Column="1">
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding EditNoteCommand}" CommandParameter="{Binding SelectedNote}" />
</DataGrid.InputBindings>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="1*" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Content" Width="3*" Binding="{Binding Content}"/>
</DataGrid.Columns>
</DataGrid>
<ToolBar Grid.Row="1" Grid.Column="1" Margin="5">
<Button Content="Create" Command="{Binding CreateNewNoteCommand}"/>
<Separator />
<Button Content="Delete" Command="{Binding DeleteNoteCommand}" CommandParameter="{Binding SelectedNote}"/>
<Separator />
<Button Content="Find" Command="{Binding FindNoteCommand}"/>
</ToolBar>
</Grid>
</Window>
FindWindowDialog.xaml(view)
<Window x:Class="NotesARK6.ViewModel.Dialogs.FindWindowDialog"
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:NotesARK6.ViewModel.Dialogs"
mc:Ignorable="d"
Title="Find" Height="250" Width="400"
WindowStartupLocation="CenterScreen"
Topmost="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="35"/>
<RowDefinition Height="60"/>
<RowDefinition Height="50"/>
<RowDefinition Height="90"/>
</Grid.RowDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding SearchByName}" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="5" Content="Search by name" Grid.Column="0"></CheckBox>
<CheckBox IsChecked="{Binding SearchByContent}" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="5" Content="Search by content" Grid.Column="1"></CheckBox>
</Grid>
<TextBox Text="{Binding SearchString}" Margin="5" Grid.Row="2" Grid.Column="1"/>
<Button Command="{Binding FindNotesCommand}" Margin="5" Content="Find" Grid.Row="3" Grid.Column="1" />
</Grid>
</Window>
FindWindowDialogViewModel.cs
public class FindWindowDialogViewModel : INotifyPropertyChanged
{
private string searchString;
private bool searchByName;
private bool searchByContent;
//Controll Commands
public ControllComands FindNotesCommand { get; private set; }
//Controll Commands
public FindWindowDialogViewModel()
{
FindNotesCommand = new ControllComands(FindNote);
}
public string SearchString
{
get
{
return searchString;
}
set
{
searchString = value;
OnPropertyChanged();
}
}
public bool SearchByName
{
get
{
return searchByName;
}
set
{
searchByName = value;
OnPropertyChanged("SearchByName");
}
}
public bool SearchByContent
{
get
{
return searchByContent;
}
set
{
searchByContent = value;
OnPropertyChanged("SearchByContent");
}
}
public void FindNote()
{
MessageBox.Show(SearchByName.ToString() + " " + SearchByContent.ToString());
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
How can I use the command contained in FindWindowDialogViewModel to hide rows in the DataGrid MainWindowView ?
I would like something like this: (this is pseudocode)
public void FindNote()
{
foreach(var row in MainWindow.DataGrid.Rows)
{
string searchingText = FindNoteDialog.TextBox.Text;
if (!row.Content.Contains(searchingText))
{
row.Visibillity = false;
}
}
}
For this purpose collections provide filtering via their views (see Binding to collections and CollectionView API remarks to learn more).
Filtering using the collection's view has much better performance than hiding rows or removing items from the original collection.
To do so, you must get the ICollectionView of the source collection
Note.cs
class Note
{
public string Summary { get; set; }
public DateTime Timestamp { get; set; }
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Note> Notes { get; }
public string SearchKey { get; set; }
public ICommand FilterNotesCommand => new RelayCommand(ExecuteFilterNotes);
private void ExecuteFilterCommands(object commandParameter)
{
ICollectionView notesView = CollectionViewSource.GetDefaultView(this.Notes);
notesView.Filter = item => (item as Note).Summary.Contains(this.SearchKey);
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding SearchKey}" />
<Button Command="{Binding FilterNotesCommand}" Content="Filter Table" />
<DataGrid ItemsSource="{Binding Notes}" />
</StackPanel>
</Window>

CollectionView, DataBinding, MVVM

Hi everyone,
I am trying to implement MVVM in my application but i am failing to load data.
I have a main page and it works fine, but once I want to navigate to the detail of my Ad and then the detail page is blank. There are my controls so i know that the pages is loaded just my data are missing. I cant seem to figure out where i have made the mistake. I am new to coding so i sometimes tend to make very silly mistakes. I been trying to solve this for past few hours otherwise would not post it over here. Thank you of any opinion.
My HomePage
<Grid Grid.Row="1" Margin="0,25,0,0">
<CollectionView x:Name="ads"
ItemsSource="{Binding AdLogEntries}"
ItemTemplate="{StaticResource HomePageTemplate}"
SelectionMode="Single"
SelectedItem="{Binding SelectedAd, Mode=TwoWay}"
SelectionChanged="CollectionView_SelectionChanged"
Margin="12,0">
</CollectionView>
</Grid>
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
BindingContext = new HomePage ViewModel();
}
async void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedAd = (AdLogEntry)e.CurrentSelection.FirstOrDefault();
if (selectedAd != null)
{
await Navigation.PushAsync(new AdDetail(selectedAd));
}
}
}
namespace Baltazar.ViewModel
{
public class HomePageViewModel : BaseViewModel
{
ObservableCollection<AdLogEntry> _adLogEntries;
public ObservableCollection<AdLogEntry> AdLogEntries { get => _adLogEntries; set { _adLogEntries = value; OnPropertyChanged(); } }
public AdLogEntry selectedAd;
public HomePageViewModel()
{
AdLogEntries = new ObservableCollection<AdLogEntry>()
{
new AdLogEntry (){Id = 0, Image = "cat.jpg", Name = "Kocka" , Description = "seda kocka na prodej, mayliva, hrava, spolecenska " ,Price = 120, },
new AdLogEntry (){Id = 1, Image = "cat2.jpg", Name = "Kocka", Description = "seda kocka na prodej, mayliva, hrava, spolecenska " ,Price = 120, },
new AdLogEntry (){Id = 2, Image = "bobtailjpg.jpg", Name = "Kocka",Description = "seda kocka na prodej, mayliva, hrava, spolecenska ", Price = 120, },
};
}
}
}
//Detail
public class AdDetailViewModel : BaseViewModel
{
AdLogEntry _adDetail;
public AdLogEntry AdDetail { get => _adDetail;set { _adDetail = value;OnPropertyChanged(); } }
public AdDetailViewModel(AdLogEntry adDetail)
{
AdDetail = adDetail;
}
}
public partial class AdDetail : ContentPage
{
//private AdLogEntry selectedAd;
public AdDetail(AdLogEntry adDetail)
{
InitializeComponent();
BindingContext = new AdDetailViewModel(adDetail);
}
}
//BaseViewModel
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected BaseViewModel()
{
}
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="300" />
<RowDefinition Height="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ffimageloading:CachedImage
x:Name="HeaderView"
Grid.Row="0"
Grid.RowSpan="2"
Aspect="AspectFill"
Source="{Binding AdLogEntries.Image}"/>
<controls:Parallax Control
x:Name="Parallax"
Grid.Row="0"
Grid.RowSpan="3">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="300" />
<RowDefinition />
</Grid.RowDefinitions>
<yummy:Pancake View
Grid.Row="1"
CornerRadius="24, 24, 0, 0"
BackgroundColor="{StaticResource BackgroundColor}"
Margin="0, 20, 0, 0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Animal NAME -->
<Label
Grid.Row="0"
Text="{Binding AdLogEntries.Name}"
/>
<!-- QUANTITY -->
<Grid
Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Grid.Column="1"
Text="{Binding AdLogEntries.Price, StringFormat='{0:C0}'}"
/>
</Grid>
<!-- ABOUT -->
<Label
Grid.Row="2"
Text="Popis"
/>
<!-- DESCRIPTION -->
<Label
Grid.Row="3"
Text="{Binding AdLogEntries.Description}"
/>
<Grid
Grid.Row="4"
Margin="0, 12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- BUY NOW BUTTON -->
<yummy:PancakeView
Grid.Column="1"
HeightRequest="48"
CornerRadius="24, 0, 24, 0"
Background Color="Accent"
Margin="24, 0, 0, 0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label
Grid.Column="1"
Text="Buy now"
/>
<Label
Grid.Column="2"
Text=">"
/>
</Grid>
</yummy:Pancake View>
</Grid>
</Grid>
</yummy:Pancake View>
</Grid>
</controls:Parallax Control>
</Grid>
</ContentPage.Content>
Your SelectedItem never changes because you are not trigger OnPropertyChange. You can change your view model logic like this:
private AdLogEntry selectedAd;
public AdLogEntry SelectedAd { get => selectedAd; set { selectedAd = value; OnPropertyChanged("SelectedAd"); } }
public ICommand AdSelectionChangedCommand => new Command(AdSelectionChanged);
private void AdSelectionChanged()
{
OnPropertyChanged("SelectedAd");
Application.Current.MainPage.Navigation.PushModalAsync(new DetailPage(SelectedAd));
}
And in your XAML:
<CollectionView x:Name="ads"
ItemsSource="{Binding AdLogEntries}"
SelectionMode="Single"
SelectionChangedCommand="{Binding AdSelectionChangedCommand}"
SelectedItem="{Binding SelectedAd, Mode=TwoWay}"
Margin="12,0">
Your detail page:
public DetailPage(AdLogEntry detail)
{
this.BindingContext = new AdDetailViewModel(adDetail);
InitializeComponent();
}
In Detail XAML:
<Label Text="{Binding AdDetail.Name}"/>
your BindingContext is AdDetailViewModel
BindingContext = new AdDetailViewModel(adDetail);
and your binding paths are like
<Label Grid.Row="3" Text="{Binding AdLogEntries.Description}"
AdDetailViewModel does not have a AdLogEntries property. It does have a AdDetail, so presumably you should use this as a binding path
<Label Grid.Row="3" Text="{Binding AdDetail.Description}"
The problem is in your detail page XAML. To be precise, the problem is in your XAML Binding Expressions. All the bindings in the detail page has wrong path set. For example, for image control it should be Source="{Binding AdDetail.Image}" instead of Source="{Binding AdLogEntries.Image}". It happens all the time and you catch these kind of errors in the output log when you debug the application.

ObservableCollection not updated to UI when property changed

Question: ObservableCollection not updated to UI when property changed
What I have tried:
XAML view:
<UserControl x:Class="FlexilineDotNetGui.Flexiline.UserControls.UCRealestate"
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"
mc:Ignorable="d"
xmlns:translations="clr-namespace:FlexilineDotNetGui.Flexiline.Translations"
xmlns:viewModels="clr-namespace:FlexilineDotNetGui.Flexiline.ViewModels"
x:Name="Realestate"
DataContext="{StaticResource vmRealestate}">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" Height="200" Margin="5"
ItemsSource="{Binding Panden, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False"
CanUserAddRows="False" IsReadOnly="True" SelectedItem="{Binding Pand}">
<DataGrid.Columns>
<DataGridTextColumn Header="{x:Static translations:UCRealestate.RegistrationType}" Binding="{Binding RegistrationType, Mode=TwoWay}" Width="Auto"/>
</DataGrid.Columns>
</DataGrid>
<Button Grid.Column="1" Grid.Row="0" Background="Transparent"
Width="20" Height="20" Padding="0" Margin="5" Command="{Binding AddPandCMD}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type viewModels:RealestateViewModel}}}">
<Image Source="/Flexiline;component/Resources/New_16x16.ico"/>
</Button>
<Button Grid.Row="1" Grid.Column="1" VerticalAlignment="Top"
Width="20" Height="20" Padding="0" Background="Transparent" Margin="5" Command="{Binding DeletePandCMD}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type viewModels:RealestateViewModel}}}">
<Image Source="/Flexiline;component/Resources/Delete_16x16.ico"/>
</Button>
<Grid Grid.Row="3" Margin="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1" Margin="0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MaxWidth="100"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
<Label Grid.Row="2" Grid.Column="2" Content="{x:Static translations:UCRealestate.RegistrationType}" HorizontalContentAlignment="Right" VerticalAlignment="Center"/>
<ComboBox Grid.Row="2" Grid.Column="3" Margin="5" ItemsSource="{Binding AardInschrijving, UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding SelectedAardInschrijving, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Description" SelectedValuePath="FlexKey"/>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</UserControl>
ViewModel :
using FlexilineDotNet.SharedDomainLogic.Models.CommunicationModels;
using FlexilineDotNetGui.Domain.Controls;
using FlexilineDotNetGui.Domain.Models;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;
namespace FlexilineDotNetGui.Flexiline.ViewModels
{
public class RealestateViewModel : ANavigationPaneSubViewModel, INotifyPropertyChanged
{
BorgRealestate _borgRealestate = new BorgRealestate();
ObservableCollection<BorgRealestate> ocPanden = new ObservableCollection<BorgRealestate>();
private DomainController _controller = DomainController.GetInstance();
public RelayCommand<object> AddPandCMD { get; private set; }
public RelayCommand<object> DeletePandCMD { get; private set; }
#region "properties"
public ObservableCollection<BorgRealestate> Panden
{
get
{
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
set
{
if (ocPanden == value)
return;
ocPanden = value;
OnPropertyChanged("Panden");
}
}
//public ObservableCollection<BorgRealestate> Panden { get; }
public BorgRealestate Pand
{
get
{
return _borgRealestate ?? (_borgRealestate = new BorgRealestate());
}
set
{
if (value == _borgRealestate) return;
_borgRealestate = value;
OnPropertyChanged();
OnPropertyChanged("Panden");
}
}
public List<DropDownItem> AardInschrijving { get { return _controller.GetDropDownItemsByType("AardInschrijving"); } }
private DropDownItem SelectedAardInschrijvingDI
{
get
{
DropDownItem test = _controller.GetDropDownItemsByType("AardInschrijving").FirstOrDefault(x => x.FlexKey == _borgRealestate?.RegistrationType);
if (test == null)
{
test = _controller.GetDropDownItemsByType("AardInschrijving").FirstOrDefault();
}
return test;
}
set
{
OnPropertyChanged();
OnPropertyChanged("Panden");
}
}
public int SelectedAardInschrijving
{
get
{
return SelectedAardInschrijvingDI.FlexKey;
}
set
{
if (value == Pand?.RegistrationType) return;
Pand.RegistrationType = value;
OnPropertyChanged();
OnPropertyChanged("Panden");
}
}
public string SelectedAardInschrijvingText
{
get
{
return SelectedAardInschrijvingDI.Description;
}
}
#endregion
#region "ctor"
public RealestateViewModel()
{
//Panden = new ObservableCollection<BorgRealestate>();
AddPandCMD = new RelayCommand<object>(o => AddPand());
DeletePandCMD = new RelayCommand<object>(o => DeletePand());
}
#endregion
#region "methods"
private void AddPand()
{
BorgRealestate newBorgRealestate = new BorgRealestate { RegistrationType = SelectedAardInschrijving };
Panden.Add(newBorgRealestate);
}
private void DeletePand()
{
Panden.Remove(Pand);
}
#endregion
}
}
Problem description:
When I Update the combobox value, it's updated in the "Panden" property when I check with a breakpoint but the datagrid in the view is not updated. The problem exists with Oneway and also with TwoWay as I defined in datagrid columns. I have both modes tried.
The views are inside a dxnavbar control, when I switch between navbar items and back then the view is updated OK.
EDIT:
When the application starts, the Panden list is indeed null, I had forgotten to mention something....
In my view there is a datagrid with a button where I add items to the observable collection where also some of the properies as example nature inscription combobox value is shown in datagrid. This button is then connected to a relay command. That is the collection that i have binded to the datagridview.
the getter of the property property is not empty and is therefore filled with the correct values, only the changes do not change in the view.
it is indeed normal that the collection is null if no "Pand" items are added to the "Panden" collection via the provided button on the UI (relaycommand). But the "Pand" item in de datagrid will not update after i change a value from the combobox after adding an item with the button to the collection.
EDIT 21/11 08:54
This is the logic for adding the pand:
private void AddPand()
{
BorgRealestate newBorgRealestate = new BorgRealestate { RegistrationType = SelectedAardInschrijving };
Panden.Add(newBorgRealestate);
}
But after i add i see the items gets added to the collection and get also updated in the collection after the combobox value changed only not in ui. (when the row in the datagrid have the focus)
In this code here:
public ObservableCollection<BorgRealestate> Panden
{
get
{
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
You create a new collection but you don't at that point call OnPropertyChanged; and you shouldn't because it would be wrong to do this here. Therefore, the UI doesn't know you've created the new collection. It looks like the first time the above getter is called is in your AddPand function. Therefore, the UI never receives an OnPropertyChanged for Panden and so does not update.
Instead of this create the collection in your constructor and you should find it will update. Also you may not need a setter at all for Panden if it is never recreated.
in constructor:
Panden = new ObservableCollection<BorgRealestate>();
Then Panden property becomes:
public ObservableCollection<BorgRealestate> Panden { get; }
Which can be simplified to:
public ObservableCollection<BorgRealestate> Panden { get; } = new ObservableCollection<BorgRealestate>();
In the Viewmodel:
That short piece of code did the trick:
CollectionViewSource.GetDefaultView(ocPanden).Refresh();
Replace this:
public ObservableCollection<BorgRealestate> Panden
{
get
{
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
set
{
if (ocPanden == value)
return;
ocPanden = value;
OnPropertyChanged("Panden");
}
}
With this:
public ObservableCollection<BorgRealestate> Panden
{
get
{
if(ocPanden != null)
{
CollectionViewSource.GetDefaultView(ocPanden).Refresh(); //This will do the trick
}
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
set
{
if (ocPanden == value)
return;
ocPanden = value;
OnPropertyChanged("Panden");
}
}
Unfortunately I have not yet found out where the cause really comes from?
If someone can know that then leave a message?

Binding in WPF ListBox not updating TextBox, when data is changed

I have a list of buildings. Buildings have their own class (core), and are saved in an ObservableCollection. Building are displayed in the list, but when I change a variable which is visible in the list, that variable doesn't change in xaml.
Here is the source of class:
public class core
{
// core ----------------------------------------------
static public ObservableCollection<core> cores { get; set; } = new ObservableCollection<core>();
string namef = "building";
public core()
{
cores.Add(this);
}
public string Namef
{
get { return namef; }
set { namef = value; }
}
}
In wpf - xaml:
<Page x:Class="idle.pages.game"
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:idle.pages"
mc:Ignorable="d"
d:DesignHeight="454.259" d:DesignWidth="757.012"
Title="game">
<Grid Margin="0" x:Name="gridx">
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TabControl x:Name="tabControl">
<TabItem Header="Budovy">
<Grid Background="#FFE5E5E5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox x:Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10,7" HorizontalAlignment="Stretch" Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Column="0" Grid.Row="0" Grid.RowSpan="3" CornerRadius="2"/>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Namef}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Frame x:Name="frame" Content="Frame" Grid.Column="1" NavigationUIVisibility="Hidden" Source="/idle;component/pages/Building.xaml"/>
</Grid>
</TabItem>
</TabControl>
</Grid>
and in wpf - c#:
public partial class game : Page
{
public game()
{
InitializeComponent();
new core() { Namef = "b1"};
new core() { Namef = "b2"};
new core() { Namef = "b3"};
core.start(this);
listBox.ItemsSource = xxx;
}
public ObservableCollection<core> xxx { get; set; }
}
Im sure, that the variable inside of the class is being changed, but xaml not. What's wrong?
You need to implement the INotifyPropertyChanged interface and invoke the PropertyChanged event in the setters of all properties that can change (and you want to have updated in the UI). If for example you want to see changes to Namef, you'd have to implement it like this:
public class core : INotifyPropertyChanged
{
static public ObservableCollection<core> cores { get; set; }
= new ObservableCollection<core>();
string namef = "building";
public core()
{
cores.Add(this);
}
public string Namef
{
get { return namef; }
set
{
if(namef == value) return;
namef = value;
OnPropertyChanged("Namef");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(String propertyName)
{
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

How to pass data between the View and the ViewModel

I'm a newbie in the MVVM design patter I'm trying to create a simple app where a list of students is shown in the main window and I want the user to be able to add a new student to the list I have accomplished the binding of the observable collection where the students' data are but how can I create a new user by fetching the data from the textboxes and using them as a parameter in a command
Here is my View
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="NameTextBlock"
Text="Name"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock x:Name="SurnameTextBlock"
Grid.Row="1"
Text="Surname"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock x:Name="AgeTextBlock"
Grid.Row="2"
Text="Age"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBox x:Name="NameTextBox"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<TextBox x:Name="SurnameTextBox"
Grid.Row="1"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<TextBox x:Name="AgeTextBox"
Grid.Row="2"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<ListBox x:Name="StudentListBox"
Grid.ColumnSpan="2"
Grid.Row="4"
Style="{StaticResource ListBoxStyle}"
ItemsSource="{Binding StudentList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock Text="{Binding Surname}"
Grid.Column="1"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock Text="{Binding Age}"
Grid.Column="2"
Style="{StaticResource TextBlockTextStyle}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="AddButton"
Grid.Row="7"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Content="Add"
Margin="7,7,7,7"
Command="{Binding AddStudentCommand}"/>
</Grid>
And here is my ViewModel
public class MainViewModel : ViewModelBase
{
ObservableCollection<Student> studentList;
public MainViewModel()
{
//populate some sample data
studentList = new ObservableCollection<Student>()
{
new Student(){Name="John", Surname="Smith", Age="28"},
new Student(){Name="Barbara", Surname="Anderson", Age="23"}
};
}
public ObservableCollection<Student> StudentList
{
get { return studentList; }
set { RaisePropertyChanged("studentList"); }
}
Student selectedPerson;
public Student SelectedPerson
{
get { return selectedPerson; }
set
{
selectedPerson = value;
RaisePropertyChanged("SelectedPerson");
}
}
private RelayCommand _addStudentCommand;
public ICommand AddStudentCommand
{
get
{
return _addStudentCommand
?? (_addStudentCommand = new RelayCommand(() =>
{
Student student = new Student();
// here should be the logic of defining the name, surname,
// age and id of the newly created student
studentList.Add(student);
}));
}
}
}
I am using MVVMLight in the current project and there are a lot of thing that I do not understand so please explain how I should pass the data of the textboxes and what exactly should happen in the command that it is being used.
Tell me to add more of the code if necessary.
What I would do is create some properties that the textboxes are bound to in the ViewModel.
Like this for all three (in your viewmodel)
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
RaisePropertyChanged("Name");
}
}
Then, in the XAML, bind the textbox's text to this:
<TextBox x:Name="NameTextBox"
Text="{Binding Name}"
Grid.Column="1"
/>
Finally in the AddStudent Command you reference the properties in the viewmodel that are bound to the textboxes.
private RelayCommand _addStudentCommand;
public ICommand AddStudentCommand
{
get
{
return _addStudentCommand
?? (_addStudentCommand = new RelayCommand(() =>
{
Student student = new Student();
student.Name = this.Name;
student.Surname = this.Surname;
student.Age = this.Age;
// here should be the logic of defining the name, surname,
// age and id of the newly created student
_StudentList.Add(student);
}));
}
}
(cheekily posted from my comment)
As you've stated, you've not had much experience and don't understand much with MVVM, so although I could answer this question, I think the best thing I could do is provide you with a few links to watch :
youtube.com/watch?v=BClf7GZR0DQ
and
channel9.msdn.com/blogs/kreekman/
The second video is from the guy who wrote MVVM-light.
They basically cover the same material, but both bring a slightly different perspective!

Categories

Resources