Wrong item selection in ListBox (Windows Phone, Caliburn.Micro, Rx) - c#

I develop an app for Windows Phone 7 with using of Caliburn Micro and Reactive Extensions.
The app has a page with a ListBox control:
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Views:ItemView Margin="0,12,0,0" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I'm using the next ItemView as a DataTemplate:
<UserControl ...>
<Grid x:Name="LayoutRoot"
cal:Message.Attach="[Event Tap] = [Action SelectItem]">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Style="{StaticResource PhoneTextLargeStyle}"
Text="{Binding Name}"
TextWrapping="Wrap" />
<TextBlock Grid.Column="1"
Foreground="{StaticResource PhoneDisabledBrush}"
Style="{StaticResource PhoneTextLargeStyle}"
Text="{Binding Id}" />
</Grid>
</UserControl>
And the corresponding ItemViewModel looks like this:
public class ItemViewModel
{
private readonly INavigationService _navigationService;
public int Id { get; private set; }
public string Name { get; private set; }
public ItemViewModel(Item item)
{
Id = item.Id;
Name = item.Name;
_navigationService = IoC.Get<INavigationService>();
}
public void SelectItem()
{
_navigationService.UriFor<MainViewModel>()
.WithParam(x => x.Id, Id)
.Navigate();
}
}
}
The ListBox populates with items:
public class ListViewModel : Screen
{
private readonly IItemsManager _itemsManager;
private List<ItemViewModel> _items;
public List<ItemViewModel> Items
{
get { return _items; }
private set
{
_items = value;
NotifyOfPropertyChange(() => Items);
}
}
public ListViewModel(IItemsManager itemsManager)
{
_itemsManager = itemsManager;
}
protected override void OnViewReady(object view)
{
base.OnViewReady(view);
Items = null;
var list = new List<ItemViewModel>();
_itemsManager.GetAll()
.SubscribeOn(ThreadPoolScheduler.Instance)
.ObserveOnDispatcher()
.Subscribe((item) => list.Add(new ItemViewModel(item)),
(ex) => Debug.WriteLine("Error: " + ex.Message),
() =>
{
Items = list;
Debug.WriteLine("Completed"));
}
}
}
And here the problems begin.
_itemsManager returns all items correctly. And all items correctly displayed in the ListBox. There is ~150 items.
When I tap on an item then SelectItem method in the corresponding ItemViewModel must be called. And all works fine for first 10-20 items in ListBox. But for all the next items SelectItem method is called in absolutely incorrect ItemViewModel. For example, I tap on item 34 and SelectItem method is called for item 2, I tap 45 - method is called for item 23, and so on. And there is no no dependence between items.
I already head breaks in search of bugs. In what could be the problem?

The solution was found after reading the discussion forum and the page in documentation of Caliburn.Micro.
All problems were because of Caliburn.Micro's Conventions.
To solve the problem I've added to the DataTempalate the next code: cal:View.Model={Binding}. Now part of the page with the ListBox looks like this:
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Views:ItemView Margin="0,12,0,0" cal:View.Model={Binding}/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I think it's not a perfect answer. So I'll be glad if someone can provide better answer and explanation.

Related

How to insert items inside listbox withing another listbox on button click

I have a Listbox which is bound to a DataTemplate that has another Listbox on it.
On DataTemplate there is a button that I want to use for adding items to DataTemplate ListBox, but I can't find a solution to do this.
Here is my listbox:
<Button Width="200" Content="Add Question" x:Name="btnAddQuestion" Click="btnAddQuestion_Click"/>
<StackPanel Orientation="Horizontal">
<ListBox Margin="5" x:Name="lvQuestions" ItemTemplate="{StaticResource TemplateQuestionTitle}">
</ListBox>
</StackPanel>
And this is DataTemplate:
<DataTemplate x:Key="TemplateQuestionTitle">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox materialDesign:HintAssist.Hint="Enter question" MinWidth="200" Style="{StaticResource MaterialDesignFloatingHintTextBox}"/>
<Button Content="+" Command="{Binding Source={x:Reference ThisPage},Path=DataContext.Command}" />
</StackPanel>
<ListBox ItemsSource="{Binding MyItems}" MinHeight="50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox>
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
This is code behind on my page:
public partial class UIBuilder:Window
{
private CommandVm _commandVm;
public UIBuilder()
{
InitializeComponent();
_commandVm = new CommandVm();
DataContext = _commandVm;
}
private void btnAddQuestion_Click(object sender, RoutedEventArgs e)
{
lvQuestions.Items.Add(null);
}
}
I have implemented this code on my ViewModel in order to add items to datatemplate ListBox:
public class CommandVm
{
public ObservableCollection<TextBox> MyItems { get; set; }
public CommandVm()
{
MyItems = new ObservableCollection<TextBox>();
Command = new RelayCommand<TextBox>(Execute);
}
private void Execute(TextBox textBox)
{
MyItems .Add(textBox);
}
public ICommand Command { get; set; }
}
I use to catch the Execute() function on button "+" click command, but my code doesn't add any ListBox item.
MyItems is a property of the parent view model which means that you should bind to it like this:
<ListBox ItemsSource="{Binding DataContext.MyItems,
RelativeSource={RelativeSource AncestorType=Window}}" MinHeight="50">
This also means that you are using one single collection of items for all questions. Besides this obvious design flaw, a view model should not contain any TextBox elements. This basically breaks what the MVVM pattern is all about.
What you should do to make this example MVVM compliant is to create a Question class that has a collection of items, e.g.:
public class Question
{
public Question()
{
AddAnswerCommand = new RelayCommand<object>(Execute);
}
private void Execute(object obj)
{
Items.Add(new Answer());
}
public ObservableCollection<Answer> Items { get; }
= new ObservableCollection<Answer>();
public ICommand AddAnswerCommand { get; }
}
public class Answer { }
The window's view model should then have a collection of questions:
public class CommandVm
{
public CommandVm()
{
AddQuestionCommand = new RelayCommand<object>(Execute);
}
public ObservableCollection<Question> Questions { get; }
= new ObservableCollection<Question>();
public ICommand AddQuestionCommand { get; }
private void Execute(object obj)
{
Questions.Add(new Question());
}
}
The view and the bindings could then be defined like this:
<Window.Resources>
<DataTemplate x:Key="TemplateQuestionTitle">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox MinWidth="200" />
<Button Content="+" Command="{Binding AddAnswerCommand}" />
</StackPanel>
<ListBox ItemsSource="{Binding Items}" MinHeight="50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Width="200" Content="Add Question" Command="{Binding AddQuestionCommand}"/>
<ListBox Margin="5"
ItemsSource="{Binding Questions}"
ItemTemplate="{StaticResource TemplateQuestionTitle}" />
</StackPanel>
This setup lets you add individual elements to each separate question.

UWP - MVVM - Remove ListView item using ItemTemplate button

I have a screen displaying a list of items on which the user can click a button to remove the corresponding item from the list.
I am trying to do so using MVVM.
But the item is not aware of the containing list when it gets the action.
I saw some answers here and there, but none of them using out of the box MVVM features I have in my environment
For example that one using PRISM (don't know if I should use that too, is it standard?):
How to properly remove Items from a ListView when the ItemTemplate is a User Control?
Here is the XAML:
<ListView ItemsSource="{Binding MyItemList}" SelectionMode="None" ScrollViewer.VerticalScrollMode="Disabled" ItemContainerTransitions="{x:Null}">
<ListView.ItemTemplate>
<DataTemplate >
<Grid Grid.Row="1" HorizontalAlignment="Stretch" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding ItemClass.Property01, Mode=TwoWay}" />
<Button Grid.Column="1" Command="{Binding RemoveItemCommand}" >
<SymbolIcon Symbol="Cancel" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And here is the ModelView list:
private static ObservableCollection<ItemClass> _MyItemList = new ObservableCollection<ItemClass> {
new ItemClass{ Property01 = "Sometext" }
};
public ObservableCollection<ItemClass> MyItemList { get { return _MyItemList; } }
And I want to be able to perform the following (the example of code from the main model view, I could create an item model view if necessary for solving):
public IMvxCommand RemoveItemCommand { get; private set; }
public MyViewModel(IUserDialogs dialogs)
{
RemoveItemCommand = new MvxCommand(RemoveItem);
}
public void RemoveItem(object theItem) { MyItemList.Remove(theItem); }
Add x:Name="listView" attribute to your ListView, then in the template
<Button Grid.Column="1"
Command="{Binding ElementName=listView, Path=DataContext.RemoveItemCommand}"
CommandParameter="{Binding}" >
However, when I face problems like this, I usually just use code behind instead. The reason for that, I can use debugger for C# code in visual studio, but debugging these complex bindings is much harder. Here’s a C# version, the code is IMO cleaner, and easier to debug:
void removeItem_Click( object sender, RoutedEventArgs e )
{
object i = ((FrameworkElement)sender).DataContext;
( this.DataContext as MyViewModel )?.RemoveItem( i );
}
Or maybe that's just my personal preference.
It would be better to have a context menu item on the list view (or a delete button on the page somewhere) to delete the currently selected item(s). You can then get the selection from the list view.
Alternatively you could attach the context menu to the list view item in PrepareContainterForItemOverride (and detach it in the other Override method)
That would be a more standards interaction style.
If you must have the button inside the list view item, then the easiest way to get the list item would probably be to use a visual tree helper to go up from the button to the list view item and then get the actual item from the list view item.
Thanks for all the hints,
Using Soonts answer, I was able to develop a fast solution,
Here is what the final implementation looks like for reference for whoever wants to copy/paste/adapt (note I did not test code as I replaced variables/functions names):
XAML:
<ListView x:Name="ItemClass_ListView" ItemsSource="{Binding MyItemList}" SelectionMode="None" ScrollViewer.VerticalScrollMode="Disabled" ItemContainerTransitions="{x:Null}">
<ListView.ItemTemplate>
<DataTemplate >
<Grid Grid.Row="1" HorizontalAlignment="Stretch" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding ItemClass.Property01, Mode=TwoWay}" />
<Button Grid.Column="1" Command="{Binding ElementName=ItemClass_ListView, Path=DataContext.RemoveItemCommand}" CommandParameter="{Binding}" >
<SymbolIcon Symbol="Cancel" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel:
public class MyViewModel : BaseViewModel, INotifyPropertyChanged
{
public IMvxCommand RemoveItemCommand { get; private set; }
public MyViewModel()
{
// Initializing Commands
RemoveItemCommand = new MvxCommand<ItemClass>(OnRemoveItemClick);
}
public void OnRemoveItemClick(ItemClass anItem)
{
// Do stuff...
}
private static ObservableCollection<ItemClass> _MyItemList = new ObservableCollection<ItemClass> {
new ItemClass(),
new ItemClass()
};
public ObservableCollection<ItemClass> MyItemList
{
get { return _MyItemList; }
}
}

How to bind ItemsControl of Labels to an ObservableCollection

I have an ItemsControl who has an ItemsSource that is an ObservableCollection. The DataTemplate contains Label controls. My goal is to set the Content property of each of these Labels to the elements in the ObservableCollection but right now, the Content is entirely blank for each of the Label.
It is worth noting that this ItemsControl is nested within another, parent ItemsControl, but let me show:
<ItemsControl ItemsSource={Binding StudentCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
// This is the ItemsControl that is not working properly with the Labels
<ItemsControl ItemsSource="{Binding StudentActivitiesCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Sport, UpdatedSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.Template>
</ItemsControl>
This is my StudentsActivities class:
public class StudentActivities : INotifyPropertyChanged
private string sport;
public string Sport
{
get
{
return this.sport;
}
set
{
this.sport = value;
OnPropertyChanged("Sport");
}
}
}
}
And my working View Model:
private ObservableCollection<StudentActivities> studentActivitiesCollection;
public ObservableCollection<StudentActivities> StudentActivitiesCollection
{
get
{
if (studentActivitiesCollection == null)
studentActivitiesCollection = new ObservableCollection<StudentActivities>();
return studentActivitiesCollection;
}
}
This is the method I am using to populate my ObservableCollection in my ViewModel:
private void PopulateStudentActivitiesCollection(ObservableCollection<Student> Students)
{
foreach (Student s in Students)
{
StudentActivitiesCollection.Add(new StudentActivities () { Sport = StudentSport });
}
}
}
Change
<ItemsControl ItemsSource={StudentCollection}">
to
<ItemsControl ItemsSource={Binding StudentCollection}">
and
<Label Content="{Binding Sport, UpdatedSourceTrigger=PropertyChanged}"/>
to
<Label Content="{Binding Sport}"/>
The last change is not needed but not necessary either.

using UserControl as item template for ItemsControl

OK, I think similar questions have already been asked, but I can't get this to work. I have a View with an ItemsControl like this:
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Content="Model Health Report:" Margin="10,10,10,0" Height="26" VerticalAlignment="Top" FontWeight="Bold"/>
<ItemsControl Grid.Row="1"
ItemsSource="{Binding HealthReports, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type hr:HealthReportSummaryControl}"/>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
That has a view model behind it like this:
public class CommunicatorViewModel : ViewModelBase
{
public ObservableCollection<HealthReportSummaryViewModel> HealthReports { get; set; }
public CommunicatorModel Model { get; set; }
public CommunicatorViewModel(HealthReportData data)
{
Model = new CommunicatorModel();
HealthReports = new ObservableCollection<HealthReportSummaryViewModel>
{
new HealthReportSummaryViewModel {Title = "View1", Description = "Some desc."},
new HealthReportSummaryViewModel {Title = "View2", Description = "Some desc."}
};
}
}
As you can see I am binding it to an ObservableCollection of HealthReportSummaryViewModel objects. These are populated in the constructor. I checked the objects at runtime, they are correct.
The actual control looks like this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0"
Fill="{Binding FillColor}"
Margin="2"/>
<Rectangle Grid.Column="1"
Fill="DarkGray"
Margin="0,2"/>
<Label Content="{Binding Title}"
Grid.Column="2"
Margin="5,0,10,0"
VerticalAlignment="Top"/>
<TextBlock Grid.Column="2"
Margin="5,10,10,0"
TextWrapping="Wrap"
Text="{Binding Description}"/>
</Grid>
With a view model:
public class HealthReportSummaryViewModel : ViewModelBase
{
private System.Windows.Media.Color _fillColor;
public System.Windows.Media.Color FillColor {
get { return _fillColor; }
set { _fillColor = value; RaisePropertyChanged(() => FillColor); }
}
private string _title;
public string Title
{
get { return _title; }
set { _title = value; RaisePropertyChanged(() => Title); }
}
private string _description;
public string Description
{
get { return _description; }
set { _description = value; RaisePropertyChanged(() => Description); }
}
}
I am getting no exceptions, but my window has only empty items. There is a rectangle in the user control that is not dependent on data binding so perhaps this is an issue with the size of the content? I can't figure this out. It's all blank. Do I need to somehow set the size for each ItemsControl item, or will they just adjust to size of the grid they are placed in? What am i missing here? All help will be appreciated.
Your DataTemplate definition is wrong:
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type hr:HealthReportSummaryControl}"/>
</ItemsControl.ItemTemplate>
This defines an empty data template for the items of the HealthReportSummaryControl type.
Instead, you should define it like that:
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type hr:HealthReportSummaryViewModel}">
<hr:HealthReportSummaryControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
This defines a template for the HealthReportSummaryViewModel items.

how do I access a radiobutton inside a ListBoxItem in windows phone 7

Please review the code for the ListBox I am using
<ListBox Name="listBoxDefaultAcc" HorizontalAlignment="Left" VerticalAlignment="Top" Width="450" Height="410">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="60" Width="450">
<RadioButton Content="{Binding}" GroupName="defaultAcc" HorizontalAlignment="Left" VerticalAlignment="Center" Height="80" Width="450" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now I want to access the content property of the RadioButton from codebehind.
The ListBoxItems are getting filled dynamically from the codebehind with the following code:
listBoxDefaultAcc.ItemsSource = from acc in db.Table<Accounts>()
select acc.accName;
Please help me out with this.
You can use the VisualTreeHelper and drill down to the control. This is not recommended though.
Better is to only bind to the properties of the controls in you datatemplate and then retrieve the values by getting the binded values. Technically in this case, if you would want to change the content of the radiobutton then you would need to change the item in the itemssource
Can you explain what you are trying to archieve by getting the content of the radiobutton?
Edit**********
<ListBox Name="listBoxDefaultAcc" HorizontalAlignment="Left" VerticalAlignment="Top" Width="450" Height="410">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="60" Width="450">
<RadioButton Content="{Binding Name}" IsChecked="{Binding Selected, Mode=TwoWay}" GroupName="defaultAcc" HorizontalAlignment="Left" VerticalAlignment="Center" Height="80" Width="450" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
public partial class Home : Page
{
public Home()
{
InitializeComponent();
var items = new List<SomeClass>();
items.Add(new SomeClass() {Name = "a"});
items.Add(new SomeClass() {Name = "b"});
items.Add(new SomeClass() {Name = "c"});
listBoxDefaultAcc.ItemsSource = items;
}
// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
private void testButton_Click(object sender, RoutedEventArgs e)
{
var items = (List<SomeClass>)listBoxDefaultAcc.ItemsSource;
var selectedItem = items.Where(x => x.Selected).FirstOrDefault();
}
class SomeClass
{
public string Name { get; set; }
public bool Selected { get; set; }
}
}
You should be using DataBinding. You should bind Content to a property, that represents content, of an object, you are setting as item.
This way, you dont have to care about ListBoxes or Templates or anything. You are simply manipulating objects, and theese changes get reflected in the GUI.

Categories

Resources