How to set specific item in a ListView using MVVM? - wpf

I have a ListView of Button elements like this:
<ListView ItemsSource="{Binding DummyModelList}" SelectedItem="{Binding SelectedItem}">
<ListViewItem >
<Button Name="test" Grid.Row="0" Grid.Column="10" Grid.ColumnSpan="4" Grid.RowSpan="4" VerticalAlignment="Center" Background="Transparent" Command="{Binding DataContext.TestCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}">
<Button.Template>
<ControlTemplate>
<Grid RenderTransformOrigin="0.5,0.5" x:Name="bg">
<Image Source="{Binding DataContext.SlidOnOffImg , RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}"/>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</ListViewItem >
In my ViewModel:
public class MyViewModel:BindableBase
{
private ObservableCollection<Slide> dummyModelList;
public ObservableCollection<Slide> DummyModelList
{
get { return dummyModelList; }
set
{
dummyModelList = value;
OnPropertyChanged(() => DummyModelList);
}
}
public string SlidOnOffImg { get; set; }
private int selectedItem;
public int SelectedItem //this property stores currently selected item from thumbnails list
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged(() => SelectedItem);
}
public CompositeCommand TestCommand { get; set; }
public MyViewModel(IRegionManager regionManager, IEventAggregator eventAggregator) //constructor
{
dummyModelList = new ObservableCollection<ISlide>() { new Slide(), new Slide(), new Slide()};
TestCommand = new CompositeCommand();
TestCommand.RegisterCommand(new DelegateCommand(ChangeImage));
}
private void ChangeImage()
{
if (dummyModelList.ElementAt(selectedItem).SlideExcluded)
dummyModelList.ElementAt(selectedItem).SlideExcluded = false;
else
dummyModelList.ElementAt(selectedItem).SlideExcluded = true;
SlidOnOffImg = dummyModelList.ElementAt(selectedItem).SlideONOFFImgPath;
OnPropertyChanged(() => SlidOnOffImg);
}
}
In my model:
public class Slide : ISlide
{
public bool SlideExcluded { get; set; }
public string SlideONOFFImgPath
{
get
{
if(SlideExcluded)
return "/Assets/Visible_OFF.png";
else
return "/Assets/Visible_ON.png";
}
}
}
My goal is to have button's image to toggle between two image paths on button click. It works, but the problem is that all the buttons in the list change the image path on some button click. I want only the one that is clicked to change the image path.
How to solve this?

You need to be binding to SelectedIndex instead of SelectedItem in your XAML. SelectedIndex holds a reference to the index for the item in the list, SelectedItem holds a reference to the object in the list. Either that, or change your view model to work with the actual object rather than the index.

Related

Caliburn micro listbox multiple selection MVVM

I have a ListBox containing Name. Now I need to select multiple items from ListBox.
ViewModel.CS
private Person selectedListOfPeople_;
public Person SelectedListOfPeople
{
get
{ return selectedListOfPeople_;}
set
{ this.SetProperty(ref selectedListOfPeople_, value, nameof(SelectedListOfPeople));}
}
private ObservableCollection<Person> listOfPeople_;
public ObservableCollection<Person> ListOfPeople
{
get { return listOfPeople_; }
set
{
this.SetProperty(ref listOfPeople_, value, nameof(ListOfPeople));
}
}
public ShellViewModel()
{
ListOfPeople = new ObservableCollection<Person>
{
new Person("ABC"),new Person("DEF"),new Person("GHI"),new Person("JKL")
};
}
public class Person : Screen
{
private string personName_;
public string PersonName
{
get { return personName_; }
set { this.SetProperty(ref personName_, value, nameof(PersonName)); }
}
public Person(string personName)
{
PersonName = personName;
}
private bool isSelected_;
public bool IsSelected
{
get { return isSelected_; }
set { this.SetProperty(ref isSelected_, value, nameof(IsSelected)); }
}
}
View.XAML
<Grid Width="500" Height="500" Background="LightBlue">
<ListBox x:Name="ListOfPeople" SelectionMode="Multiple" Height="300" Width="300" Margin="120,100,80,100">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PersonName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
in that SelectedListOfPeople is not called when the second item is selected in ListBox set to Multiple selections. How can I make sure that this event is raised every time the user makes a selection in ListBox?
One way of doing this would be to break from the convention available in that framework and bind the property manually.
But first you would need to update the property for multiselect in the view model
private ObservableCollection<Person> selectedListOfPeople;
public ObservableCollection<Person> SelectedListOfPeople {
get { return selectedListOfPeople; }
set { this.SetProperty(ref selectedListOfPeople, value, nameof(SelectedListOfPeople)); }
}
private ObservableCollection<Person> listOfPeople;
public ObservableCollection<Person> ListOfPeople {
get { return listOfPeople; }
set { this.SetProperty(ref listOfPeople, value, nameof(ListOfPeople)); }
}
public ShellViewModel() {
ListOfPeople = new ObservableCollection<Person> {
new Person("ABC"),
new Person("DEF"),
new Person("GHI"),
new Person("JKL")
};
SelectedListOfPeople = new ObservableCollection<Person>();
}
And then bind to the desired property in the view's XAML
<ListBox x:Name="ListOfPeople" SelectionMode="Multiple"
Height="300" Width="300" Margin="120,100,80,100"
SelectedItems = "{Bining SelectedListOfPeople}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PersonName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
The convention will bind the items source or the ListBox and the manual binding of the SelectedItems will provided the desired behavior.

How to test ItemsControl with TestStack.White.UIItems

So I'm trying to test UI WPF application. I'm using TestStack.White framework for testing. UI has custom control DragDropItemsControl. This control inherits from ItemsControl. So how can I test this control.
<wpf:DragDropItemsControl x:Name="uiTabsMinimizedList"
Margin="0 0 0 5"
VerticalAlignment="Top"
AllowDropOnItem="False"
DragDropTemplate="{StaticResource TemplateForDrag}"
ItemDropped="uiTabsMinimizedList_ItemDropped"
ItemsSource="{Binding ElementName=uiMain,
Path=MinimizedTabs}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
TextBlock.Foreground="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl},
Path=Foreground}">
<wpf:DragDropItemsControl.ItemTemplate>
<DataTemplate>
<Border >
<TextBlock Cursor="Hand" Text="{Binding Panel.Label}" />
</Border>
</DataTemplate>
</wpf:DragDropItemsControl.ItemTemplate>
</wpf:DragDropItemsControl>
Can we test?
You have to create your own AutomationPeer for your DragDropItemsControl and for your custom control item then you will be able to define the AutomationId as an identifier of the your item object.
public class DragDropItemsControl : ItemsControl
{
protected override AutomationPeer OnCreateAutomationPeer()
{
return new DragDropItemsAutomationPeer(this);
}
}
The custom AutomationPeer class for your control.
public class DragDropItemsControlAutomationPeer : ItemsControlAutomationPeer
{
public DragDropItemsControlAutomationPeer(DragDropItemsControl owner)
: base(owner)
{
}
protected override string GetClassNameCore()
{
return "DragDropItemsControl";
}
protected override ItemAutomationPeer CreateItemAutomationPeer(object item)
{
return new DragDropItemsControlItemAutomationPeer(item, this);
}
}
The custom AutomationPeer class for your control items.
The important part here is the implementation of the method GetAutomationIdCore().
public class DragDropItemsControlItemAutomationPeer : ItemAutomationPeer
{
public DragDropItemsControlItemAutomationPeer(object item, ItemsControlAutomationPeer itemsControlAutomationPeer)
: base(item, itemsControlAutomationPeer)
{
}
protected override string GetClassNameCore()
{
return "DragDropItemsControl_Item";
}
protected override string GetAutomationIdCore()
{
return (base.Item as MyTestItemObject)?.ItemId;
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return base.GetAutomationControlType();
}
}
For the following xaml code
<local:MyItemsControl x:Name="icTodoList" AutomationProperties.AutomationId="TestItemsControl">
<local:MyItemsControl.ItemTemplate>
<DataTemplate>
<Border >
<TextBlock Cursor="Hand" Text="{Binding Title}" />
</Border>
</DataTemplate>
</local:MyItemsControl.ItemTemplate>
</local:MyItemsControl>
Init in code behind
public MyMainWindow()
{
InitializeComponent();
List<MyTestItemObject> items = new List<MyTestItemObject>();
items.Add(new MyTestItemObject() { Title = "Learning TestStack.White", ItemId="007" });
items.Add(new MyTestItemObject() { Title = "Improve my english", ItemId = "008" });
items.Add(new MyTestItemObject() { Title = "Work it out", ItemId = "009" });
icTodoList.ItemsSource = items;
}
public class MyTestItemObject
{
public string Title { get; set; }
public string ItemId { get; set; }
}
We can see in UIAVerify
Sample code to check the values
// retrieve the custom control
IUIItem theItemsControl = window.Get(SearchCriteria.ByAutomationId("008"));
if (theItemsControl is CustomUIItem)
{
// retrieve the custom control container
IUIItemContainer controlContainer = (theItemsControl as CustomUIItem).AsContainer();
// get the child components
WPFLabel theTextBlock = controlContainer.Get<WPFLabel>(SearchCriteria.Indexed(0));
// get the text value
string textValue = theTextBlock.Text;
}

RaisePropertyChange with Canvas

I'm using Prism for MVVM pattern in my WP7 app. In my ViewModel I implemented two properties:
private IconVO _selectedIcon;
public IconVO SelectedIcon {
get {
return _selectedIcon;
}
set {
_selectedIcon = value;
SelectedIconCanvas = _selectedIcon.Icon;
RaisePropertyChanged(() => this.SelectedIcon);
}
}
private Canvas _selectedIconCanvas;
public Canvas SelectedIconCanvas {
get {
return _selectedIconCanvas;
}
set {
_selectedIcon = value;
RaisePropertyChanged(() => this.SelectedIconCanvas);
}
}
where IconVO (it stores single icon information loaded from some XML file):
public class IconVO {
public string Name { get; set; }
public Canvas Icon { get; set; }
}
SelectedIcon is currently selected IconVO from ObservableCollection<IconVO> (collection is binded to ListPicker).
SelectedIconCanvas is a property which stores Canvas from SelectedIcon.Icon.
When I execute this code, application throws the ArgumentException -> The parameter is incorrect on this line:
RaisePropertyChanged(() => this.SelectedIconCanvas);
What's wrong with this code?
Thanks, fl4izdn4g
EDIT 01-02-2012
Here is XAML as you requested:
<Border Grid.Row="1" Background="{Binding SelectedColor}" >
<ContentControl Margin="40,20,300,20" Content="{Binding SelectedIconCanvas}">
<ContentControl.ContentTemplate>
<DataTemplate>
<ContentPresenter />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Border>
I've tried to replace ContentControl with:
<Button Content="{Binding SelectedIconCanvas}"/>
but it didn't help.
You should not have a UI element mentioned in a view model in MVVM. Maybe you want to reference the DataContext/view model of the selected canvas?
Just put this:
private IconVO _SelectedIcon;
public IconVO SelectedIcon
{
get { return _SelectedIcon; }
set
{
_SelectedIcon = value;
SelectedIconCanvas = _SelectedIcon.Icon;
RaisePropertyChanged("SelectedIcon");
}
}
private Canvas _SelectedIconCanvas;
public Canvas SelectedIconCanvas
{
get { return _SelectedIconCanvas; }
set
{
_SelectedIconCanvas = value;
RaisePropertyChanged("SelectedIconCanvas");
}
}
Sorry I changed your Properties Names (I have the C# preference)

Silverlight MVVM Light Listbox item Click Event

I'm using the MVVM Light Toolkit with Silverlight.
On my UserControl I have a ListBox that displays a list of files. Each file has a delete image next to the file name. In the DataTemplate for the listbox I have an image (or can use a button) and a TextBlock.
So I want to capture using the event when the user will clicks on the image(or button with image) to remove the file from the list of files.
But I cannot seem to capture the event. Maybe this is due to having the SelectedItem Event on the listbox?
public class MainViewModel : ViewModelBase
{
#region Properties
public const string SelectedListBoxFilePropertyName = "SelectedUploadFile";
private UploadFile _selectedUploadFile = null;
public UploadFile SelectedUploadFile
{
get
{
return _selectedUploadFile;
}
set
{
if (_selectedUploadFile == value)
return;
_selectedUploadFile = value;
RaisePropertyChanged(SelectedListBoxFilePropertyName);
}
}
public const string UploadFilesPropertyName = "UploadFiles";
private ObservableCollection<UploadFile> _uploadFiles = new ObservableCollection<UploadFile>();
public ObservableCollection<UploadFile> UploadFiles
{
get
{
return _uploadFiles;
}
set
{
if (_uploadFiles == value)
return;
_uploadFiles = value;
RaisePropertyChanged(UploadFilesPropertyName);
}
}
#endregion
public static ICommand BrowseCommand { get; private set; }
public static ICommand DragDropFileCommand { get; private set; }
public static ICommand RemoveCommand { get; private set; }
#region Constructor
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
UploadFiles = new UploadFileContainer().UploadFiles;
}
else
{
// Code runs "for real"
}
WireUpCommands();
}
#endregion
#region Event Handlers
private void OnBrowseFileCommand()
{
var dialog = new OpenFileDialog();
dialog.ShowDialog();
if (dialog.Files != null)
AddFiles(dialog.Files);
}
private void OnDropFileCommand(DragEventArgs e)
{
var files = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
AddFiles(files);
}
private void OnRemoveFileCommand()
{
UploadFiles.Remove(_selectedUploadFile);
}
#endregion
#region Private Methods
private void WireUpCommands()
{
BrowseCommand = new RelayCommand(OnBrowseFileCommand);
DragDropFileCommand = new RelayCommand<DragEventArgs>(e => OnDropFileCommand(e));
RemoveCommand = new RelayCommand(OnRemoveFileCommand);
UploadCommand = new RelayCommand(OnClickUploadCommand);
}
#endregion
}
<ListBox Grid.Row="1" Height="214" HorizontalAlignment="Left" AllowDrop="True" Margin="6,26,0,0" Name="UploadFilesListBox" VerticalAlignment="Top" Width="415" ItemsSource="{Binding Path=UploadFiles}" SelectedItem="{Binding Path=SelectedListBoxFile, Mode=TwoWay}" ScrollViewer.VerticalScrollBarVisibility="Auto">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Drop">
<cmd:EventToCommand Command="{Binding DragDropFileCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.Background>
<ImageBrush ImageSource="/FileUploadApplication;component/Resources/dragdrophere.png" Stretch="None" />
</ListBox.Background>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveCommand}">
<Image Source="/FileUploadApplication;component/Resources/delete.png"/>
</Button>
<Image Source="/FileUploadApplication;component/Resources/delete.png">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding RemoveCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image> <TextBlock Text=" " />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Since your ItemsSource is UploadFiles it's probably sending the event to UploadFile and not the view model the user control is bound to.
Your button is the element of the ItemTemplate. you're binding the listbox ItemsSource to the ObservableCollection. Every Itemtemplate DataContext is no MainViewModel, but UploadFile, which has no RemoveCommand.
I was solving this by adding to every item the parent object using constructor. RemoveCommand was inside the item's ViewModel and insede the remove function i was calling the parent's method to delete the item.
Not sure if that's the best solution but it worked for me.

Combobox display value in Silverlight

I have ComboBox with CheckBoxes for items.
When user checks or uncheckes boxes I want the selected values to be displayed in the ContentPresenter separated by comma.
At the the moment I have overridden ContentPresenter:
<ContentPresenter x:Name="ContentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
ContentTemplate="{StaticResource SelectedOperationsText}"/>
ContentPresenter is a part of ComboBox style by default.
Any hints on how to implement this feature?
ComboBox ItemTemplate is implemented like this:
<DataTemplate x:Key="ComboItemTemplate">
<Grid HorizontalAlignment="Left">
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Text}"/>
</Grid>
</DataTemplate>
This solution isn't ideal (for example, you can create custom control template for control inherited from combobox), but it works.
Xaml
<my:MyComboBox Width="180" ItemsSource="{Binding TestItems}" Text="{Binding SelectedItemsText}">
<my:MyComboBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left">
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Content="{Binding Text}"/>
</Grid>
</DataTemplate>
</my:MyComboBox.ItemTemplate>
</my:MyComboBox>
Hack of the combobox:
public class MyComboBox : ComboBox
{
private ContentPresenter selectedContent;
public MyComboBox()
{
this.DefaultStyleKey = typeof(ComboBox);
}
public override void OnApplyTemplate()
{
this.selectedContent = this.GetTemplateChild("ContentPresenter") as ContentPresenter;
this.RefreshContent();
base.OnApplyTemplate();
this.SelectionChanged += (s, e) =>
{
//Cancel selection
this.SelectedItem = null;
this.RefreshContent();
};
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyComboBox),
new PropertyMetadata(null, new PropertyChangedCallback((s,e)=>((MyComboBox)s).RefreshContent())));
private void RefreshContent()
{
if (this.selectedContent != null)
{
var tb = (TextBlock)this.selectedContent.Content;
tb.Text = this.Text;
}
}
}
MainViewModel
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
this.InitializeTestItems();
}
public void InitializeTestItems()
{
this.TestItems = new List<TestItemModel>{
new TestItemModel{IsChecked=true, Text="first"},
new TestItemModel{IsChecked=false, Text="second"},
new TestItemModel{IsChecked=false, Text="third"}};
this.RefreshSelectedItemsText();
foreach (var item in this.TestItems)
item.CheckChanged += (s, e) => this.RefreshSelectedItemsText();
}
private void RefreshSelectedItemsText()
{
SelectedItemsText = string.Join(", ", this.TestItems.Where(ti => ti.IsChecked).Select(ti => ti.Text));
}
public List<TestItemModel> TestItems { get; set; }
private string selectedItemsText;
public string SelectedItemsText
{
get { return selectedItemsText; }
set
{
selectedItemsText = value;
OnPropertyChanged("SelectedItemsText");
}
}
}
4.ItemViewModel
public class TestItemModel
{
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (CheckChanged != null)
CheckChanged(this, null);
}
}
public string Text { get; set; }
public event EventHandler<EventArgs> CheckChanged;
}
I did not understand what you meant by "the ContentPresenter".
If you want a combox box, with the list of selected items as its text, I can explain how my son (who's not in SO) did it:
He put a grid with a ComboBox followed by a TextBlock. The ItemTemplate of the ComboBox includes a check box with a handler for the Checked and UnChecked events. In these events, he recomputed the Text property of the TextBlock, based on the selected state of the check boxes.
Here is the XAML:
<Grid Name="LayoutRoot">
<ComboBox ItemsSource="{Binding Path=SitesList}" Name="CBsites" DropDownOpened="ComboBox_DropDownOpened" DropDownClosed="ComboBox_DropDownClosed">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=Location}" Checked="SiteCheckBox_Checked" Unchecked="SiteCheckBox_Unchecked" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Name="TXTselected" IsHitTestVisible="False" VerticalAlignment="Center" Margin="6,0,0,0" />
</Grid>
I think one can do it without the TextBlock. Hopefully, this can put you in the right direction.
I have created a codeplex project here: codeplex inspired by this blog and a number other ones, please check it out and improve it etc. Hopefully this or something like it will find it's way into the toolkit...
I prefer not needing a selection boolean in the bound data so i brought a bindable SelectedItems

Resources