I develop CRUD app for WindowsPhone 8.1. I can add data to ObservableCollection collection and this data is displayed on ListBox. I use MVVM pattern.
Full repository https://github.com/OlegZarevych/CRUD_WP81
View :
<ListBox x:Name="Storage" ItemsSource="{Binding Path=Models, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="30" Width="450">
<TextBlock x:Name="nameblock" Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And ViewModel class
public class ViewModel
{
public string NewName { get; set; }
public string NewSurname { get; set; }
public int NewAge { get; set; }
public int i=0 ;
public ObservableCollection<DataStorageModel> Models { get; set; }
//Event Handlers
public ICommand CreateClickCommand { get; set; }
public ICommand UpdateClickCommand { get; set; }
public ICommand DeleteClickCommand { get; set; }
public ViewModel()
{
CreateClickCommand = new RelayCommand(arg => CreateClickMethod());
UpdateClickCommand = new RelayCommand(arg => UpdateClickMethod());
DeleteClickCommand = new RelayCommand(arg => DeleteClickMethod());
Models = new ObservableCollection<DataStorageModel>() {};
}
private void CreateClickMethod()
{
Models.Add(new DataStorageModel() { Name = NewName, Surname = NewSurname, Age = NewAge, Count=i++ });
}
private void UpdateClickMethod()
{}
private void DeleteClickMethod()
{}
}
I want to change data and delete it. As i good understand, I need select count from ListBoxItems and delete(update) this count in ObservableCollection.
How can I work with XAML code from ViewModel class ?
How can I initiliaze Storage in ViewModel ?
Or in MVVM is the better way to resolve this problem ?
When you want to delete a model from the ListBox you typically need some way to identify the selected ListBoxItems (or models) that you want to delete; for that, consider having an IsSelected property on your models and bind it to a CheckBox inside the ListBoxItem data template.
Now, when you click on delete, the delete command can then easily look into the Models list and see which items are selected for deletion. After it deletes the items, it can then enumerate over the collection and recalculate the count value for the remaining items and update the field in the view model.
So, you don't have to access the XAML to update the count of the models. If you make the count property mutable then you wouldn't have to reinitialize the storage after you delete items from the list.
I added code t the Model
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Also added checkbox with bindin to View.
<ListBox x:Name="Storage" Background="Gray" FontSize="14" ItemsSource="{Binding Path=Models, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="60" Width="400" >
<CheckBox x:Name="checkbox" IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock x:Name="nameblock" Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But IsSelected var doesn't change when I check checkbox in item
Why ?
Related
I'm defining a list of check boxes as follows:
<ListBox Name="listBoxZone" ItemsSource="{Binding Nr5GRRCList}" Background="Azure" Margin="346,93,89,492" Grid.Column="1">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem IsSelected="{Binding IsChecked}">
<CheckBox x:Name="RRC5G_CheckBox"
Content="{Binding messageType}"
IsChecked="{Binding IsChecked}"
Checked="RRC5G_CheckBox_Checked"
Unchecked="RRC5G_CheckBox_Unchecked"
Margin="0,5,0,0"/>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Where Nr5GRRCList related code is:
public ObservableCollection<BoolStringClass> Nr5GRRCList { get; set; }
public class BoolStringClass
{
public string messageType { get; set; }
public bool IsChecked { get; set; }
}
if (Nr5GRRCList == null)
{
Nr5GRRCList = new ObservableCollection<BoolStringClass>();
}
foreach(string filter in rrc5GFilters)
{
Nr5GRRCList.Add(new BoolStringClass { messageType = filter, IsChecked = false });
}
This is working fine:
I'm trying to add an checkbox to control all checkboxes in this list:
I'll still be able to check/uncheck the checkboxes individually
I want to be able to check/uncheck the new checkbox and get all the checkboxes checked/unchecked
I tried adding the new checkbox:
<CheckBox x:Name="checkBox_NR5G_RRC" Content="RRC" Checked="HandleCheck_RRC5G" Unchecked="HandleUncheck_RRC5G" Height="Auto" Width="Auto" Margin="334,76,341,607" Grid.Column="1"/>
I can't find a way of modifying the ischecked value of each individual checkbox when the 'RRC' is checked/unchecked. All I seem to have access to is a list of BoolStringClass elements.
Any tipd would be much appreciated.
Thanks!
All I seem to have access to is a list of BoolStringClass elements
that is all data you need.
foreach(BoolStringClass item in Nr5GRRCList)
{
item.IsChecked = !item.IsChecked;
// item.IsChecked = true;
// item.IsChecked = false;
}
to see update in UI you need to add notifications in BoolStringClass, e.g by implementing INotifyPropertyChanged:
public class BoolStringClass: INotifyPropertyChanged
{
public string messageType { get; set; }
private bool _IsChecked;
public bool IsChecked
{
get { return _IsChecked; }
set
{
_IsChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I have a list called "CarEngineItems" of "SelectableItems" objects with 2 properties: ItemDescription and isSelected.
namespace DTOs
{
class CarEngines : INotifyPropertyChanged
{
public static List<SelectableItem> CarEngineItems { get; set; } = new List<SelectableItem>();
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
namespace Models
{
public class SelectableItem : INotifyPropertyChanged
{
public string ItemDescription { get; set; }
public bool IsSelected { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
I have used the below user control to create a radio button for each item in the list.
<UserControl.Resources>
<SelectableItem:SelectableItem x:Key="vm"></SelectableItem:SelectableItem>
<src:RadioButtonCheckedConverter x:Key="RadioButtonCheckedConverter" />
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=ItemDescription}"
x:Key="ListingDataView" />
<DataTemplate x:Key="GroupingHeaderTemplate">
<TextBlock Text="{Binding Path=Name}" Style="{StaticResource GroupHeaderStyle}"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl Name="RadioGroup" AutomationProperties.Name="RadioGroup" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton
Content="{Binding Path=ItemDescription}"
FlowDirection="RightToLeft"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" Style="{DynamicResource CustomRadioButton}" Margin="20,0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
I added this user control to my main window.
<uc:CommonRadioButtonGroup x:Name="EnginesGroup"></uc:CommonRadioButtonGroup>
In the code behind, I updated the source to this user control as:
InitializeComponent();
EnginesGroup.RadioGroup.ItemsSource = DTOs.CarEngines.CarEngineItems;
The CarEngineItems were added at startup.
CarEngines.CarEngineItems.Add(new SelectableItem
{
ItemDescription = "V8",
IsSelected = Properties.Settings.Default.Engines.Equals("V8")
});
CarEngines.CarEngineItems.Add(new SelectableItem
{
ItemDescription = "Electric",
IsSelected = Properties.Settings.Default.Engines.Equals("Electric")
});
The current output results in this:
On startup, the the default value V8 is selected as expected.
Proper binding happens on startup.
But on clicking the next item in group, it is also selected.
Why are both selected?
The ultimate aim is to select a check box (created dynamically) and update only the selected item to a property table on pressing a button (SAVE button). But in this case, the selection is happening for all items.
Based on #o_w 's comment, I am updating the answer.
I added a group name to the Radio Button Group by binding it to another property of SelectableItem.
<ItemsControl Name="RadioGroup" AutomationProperties.Name="RadioGroup" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton
GroupName="{Binding Path=ItemType}"
Content="{Binding Path=ItemDescription}"
FlowDirection="RightToLeft"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"
Style="{DynamicResource CustomRadioButton}" Margin="20,0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here's the updated SelectableItem.
namespace Models
{
public class SelectableItem : INotifyPropertyChanged
{
public string ItemDescription { get; set; }
public bool IsSelected { get; set; }
public string ItemType { get; set; }//New
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
i have a view that have a list view with data template
i need to set style on the selected item
but i need also when the selected item is been changed from the code it modify the selected item in the view
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" Width="300" Height="50" TextAlignment="Center"/>
<ListView Grid.Row="1" ItemsSource="{Binding List, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate >
<Border BorderThickness="1" BorderBrush="White">
<Grid Height="20" Width="30" >
<TextBlock Text="{Binding Name}"/>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
there is a list view and textblock
i need when the selectedItem changed it changed the the background of the selected item
here is the viewmodel
public class MainViewModel : ViewModelBase
{
private Item selectedItem;
public ObservableCollection<Item> List { get; set; }
string text;
public string Text
{
get { return text; }
set
{
text = value;
OnPropertyChanged("Text");
}
}
public Item SelectedItem
{
get { return selectedItem; }
set{
if (value.Name != "Test1")
{
selectedItem = value;
Text = value.Name;
}
else
{
Text = string.Format("Test1 was selected but the selected item is {0}", selectedItem==null?"null":selectedItem.Name);
}
OnPropertyChanged("SelectedItem");
}
}
public MainViewModel()
{
List = new ObservableCollection<Item>()
{
new Item("Test1","Val1"),new Item("Test2","Val2"),new Item("Test3","Val3"),new Item("Test4","Val"),
};
OnPropertyChanged("List");
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(params string[] propertyNames)
{
if (PropertyChanged != null)
{
foreach (var propertyName in propertyNames)
{
var e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
}
}
public class Item : ViewModelBase
{
public string Name { get; set; }
public string Value { get; set; }
public Item(string name, string val)
{
Name = name;
Value = val;
OnPropertyChanged("Name");
}
}
note that when the Test1 Item selected the selected item didnot changed but in the view Test1 is marked as selected
At the point your MainViewModel.SelectedItem setter is called by the view, the view has already updated its selected item in the list. The binding simply informs the VM of this fact. The fact that you don't set MainViewModel.selectedItem means nothing to the view.
You would think that raising OnPropertyChanged("SelectedItem"); would force the view to re-evaluate its selected item, but in practice this does not work. I assume is down to some optimization within WPF or to prevent cyclic binding updates. (Remember you setter is already being called as part of a binding update, and you are trying to update the binding again)
If you wish to prevent something being selected in the view, then you need to disable it within the view, before it gets down to the VM. Here is one way of doing this.
I have a class with data:
public class routedata : INotifyPropertyChanged
{
private List<double> distances;
public List<double> Distances
{
get { return this.distances; }
set
{
if (this.distances != value)
{
this.distances = value;
this.onPropertyChanged("Distances");
}
}
}
private List<string> instructions;
public List<string> Instructions
{
get { return this.instructions; }
set
{
if (this.instructions != value)
{
this.instructions = value;
this.onPropertyChanged("Instructions");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void onPropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
I'm trying to bind it to a listview like this:
<GridView Name="routeView" HorizontalAlignment="Left" Height="310" Margin="1025,318,0,0" Grid.Row="1"
VerticalAlignment="Top" Width="340" >
<ListView Name="routeList" Height="300" Width="330" ItemsSource="{Binding routeData}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Instructions}"
TextWrapping="Wrap" Width="200"/>
<TextBlock Text="{Binding Distances}"
Margin="10,0,0,0" />
<TextBlock Text="km"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</GridView>
I have in my c# code behind: routeList.datacontext = this;
but it is still not binding, only one empty row is populated in the listview. I have checked the data and it is all present. Any help would be appreciated thank you.
A ListView takes a single collection as ItemsSource, so if you want to display multiple TextBlocks for each item - you need a collection of objects with multiple text properties to bind to your DataTemplate. In your case a routeData is not a collection. Instead you need to define your item view model, e.g.
public class RoutePoint
{
public double Distance { get; set; }
public string Instruction { get; set; }
}
then you would bind your ListView.ItemSource to a List and in your DataTemplate bind it like that:
<TextBlock Text="{Binding Distance}"/>
<TextBlock Text="{Binding Instruction}"/>
You don't need to use an ObservableCollection if your collection never changes after you bind it to the ListView for the first time (SelectedItem doesn't constitute a change).
If your view is called routeView, shouldn't your DataContext be set to a new instance of routedata? Also, I suggest you use an ObservableCollection<T> for your bindable collections rather than List<T>.
I am having trouble binding my View Model to my View. I am a beginner with MVVM, but I believe I am implementing my system (almost) correctly. I have a Model that contains data, which I am getting in my View Model, and then when my page is navigated to, I am attempting to grab that View Model data and binding it to the View.
My issue is that I have a ListBox in my View with 3 objects per item, and I cannot seem to bind to it correctly for each item in my list.
MainPage.xaml
<ListBox x:Name="FavoritesListBox" ItemsSource="{Binding FavoriteItems}"
SelectionChanged="FavoritesListBox_SelectionChanged">
<StackPanel Orientation="Horizontal" Margin="12,0,12,0">
<Image x:Name="favicon" Source="{Binding Favicon}"
Width="50" Height="50"/>
<StackPanel>
<TextBlock x:Name="favoritesName" Text="{Binding Name}"
FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<TextBlock x:Name="favoritesAddress"
Text="{Binding Address}" Margin="12,0,0,0"/>
</StackPanel>
</StackPanel>
</ListBox>
MainPage.xaml.cs
public FavoritesPage()
{
InitializeComponent();
// Set the data context of the listbox control to the sample data
FavoritesListBox.DataContext = App.ViewModel;
}
App.xaml.cs
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
MainViewModel.cs
public ObservableCollection<ItemViewModel> FavoriteItems { get; private set; }
public MainViewModel()
{
//FavoriteItems = new ObservableCollection<ItemViewModel>();
FavoriteItems = Settings.FavoritesList.Value;
}
Settings.cs (The Model)
public static Setting<ObservableCollection<ItemViewModel>> FavoritesList =
new Setting<ObservableCollection<ItemViewModel>>(
"Favorites",
new ObservableCollection<ItemViewModel>());
ItemViewModel.cs
private string _favicon;
public string Favicon
{
get
{
return _favicon;
}
set
{
if (value != _favicon)
{
_favicon = value;
NotifyPropertyChanged("Favicon");
}
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (value != _name)
{
_name = value;
NotifyPropertyChanged("Name");
}
}
}
private string _address;
public string Address
{
get
{
return _address;
}
set
{
if (value != _address)
{
_address = value;
NotifyPropertyChanged("Address");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
..and this is where and how I am saving each item (which should have three properties listed in the ItemViewModel
void addToFavorites_Click(object sender, EventArgs e)
{
var favoriteItem =
new ItemViewModel{
Favicon = "",
Name = "",
Address = TheBrowser.currentUrl() };
Settings.FavoritesList.Value.Add(favoriteItem);
}
Where FavoritesList is populated using an ItemViewModel containing 3 objects. The list is being populated correctly because during debugging I can see the entities in FavoritesList, but I am having an issue calling these entities in the view model to show up in my ListBox in the view?
I believe I am binding incorrectly but I'm not sure how to fix this?
In your XAML you bind to paths Name and Address do you have these 2 properties defined in your ItemViewModel?
Update after reading your code properly:
You are not updating the datatemplate of the Items of the Listbox. This is what you need to do:
<ListBox x:Name="FavoritesListBox" ItemsSource="{Binding FavoriteItems}" SelectionChanged="FavoritesListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="12,0,12,0">
<Image x:Name="favicon" Source="{Binding Favicon}" Width="50" Height="50"/>
<StackPanel>
<TextBlock x:Name="favoritesName" Text="{Binding Name}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<TextBlock x:Name="favoritesAddress" Text="{Binding Address}" Margin="12,0,0,0"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In addition to setting your DataContext to your viewmodel, (as mentioned in the comment linking to Creating ContextBinding XAML) , you also need to have your view model implement INotifyPropertyChanged (including ItemViewModel, which you don't show in your question)