Caliburn Micro -- Change Individual Items in BindableCollection - wpf

I am displaying a collection in a listview, and each listview item has its own button for downloading the related pdf.
I want to click the button and display an indeterminate progress bar in it's place.
In my sample project, I'm merely trying to invert the item's download status when I click its button.
My viewmodel code:
private BindableCollection<IDownloadable> _downloadables;
public BindableCollection<IDownloadable> Downloadables
{
get { return _downloadables; }
set
{
_downloadables = value;
NotifyOfPropertyChange(() => Downloadables);
}
}
public void Download(IDownloadable item)
{
item.Downloading = !item.Downloading;
NotifyOfPropertyChange(() => Downloadables);
}
public ShellViewModel()
{
Downloadables = new BindableCollection<IDownloadable>();
Downloadables.Add(new DownloadString("String"));
Downloadables.Add(new DownloadString("String"));
Downloadables.Add(new DownloadString("String"));
}
My view:
<ListView Name="Downloadables"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid Background="White">
<ToggleButton Content="{Binding MyString}"
Width="50"
Height="50"
HorizontalAlignment="Center"
VerticalAlignment="Center"
cal:Message.Attach="Download($dataContext)"/>
<spark:SprocketControl Width="30"
Height="30"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AlphaTicksPercentage="50"
Interval="60"
IsIndeterminate="True"
LowestAlpha="50"
StartAngle="-90"
TickColor="LawnGreen"
TickCount="12"
TickWidth="3"
Visibility="{Binding Downloading, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
I had imagined that NotifyOfPropertyChanged would tell the collection items to update, but that doesn't appear to be the case.

This has has nothing to do with collection. The template binds to the item itself. It has no idea about the collection. This means, you need to implement notification (INotifyPropertyChanged) in the item itself.

Related

TextBox's Text Property returns null when using CommandParameter

I managed to create a Command, like so:
Code-behind
public static RoutedCommand GetValueCommand = new RoutedCommand();
private void ExecutedGetValueCommand(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Custom Command Executed");
Button b = (sender) as Button;
MessageBox.Show(b.CommandParameter.ToString());
}
private void CanExecuteGetValueCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
XAML
<UserControl.CommandBindings>
<CommandBinding Command="{x:Static local:ReturnsUserControl.GetValueCommand}"
Executed="ExecutedGetValueCommand"
CanExecute="CanExecuteGetValueCommand" />
</UserControl.CommandBindings>
<ListView>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextBlock Text="{Binding ProductDescription}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextBox x:Name="txtExchangeQuantity"
Tag="{Binding ProductBarcode}"/>
<Button Content="Add"
Tag="{Binding ProductBarcode}"
Command="{x:Static local:ReturnsUserControl.GetValueCommand}"
CommandParameter="{Binding Text,ElementName=txtExchangeQuantity}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
When I clicked the button, CommandParameter always return null, even when I put something in the text box, and I'm sure that the command is working because Custom Command Executed shows.
What I want to achieve here is to get the value of the TextBox that has the same Tag value as the Button's Tag (same barcode), because there will be multiple instances of both the TextBox and the Button, and the Tag is the only one that can pair them.
As we spoke in the comments, you should be looking for ExecutedRoutedEventArgs.Parameter instead of checking the sender in your event handler.

How to set image path of specific ityem in ListView?

I have a ListView of Button elements like this:
<ListView ItemsSource="{Binding NumberOfItems}" 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.Test_ImagePath, RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}"/>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</ListViewItem >
</ListView>
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. I tried using CommandTarget property like this:
CommandTarget="{Binding DataContext.Listview.SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListViewItem}}
but it didn't heplp.
Just to mention that I use MVVM.
How to solve this?
If you are using MVVM, I suppose you could wrap your models (As you said, integers for now) with a wrapper like this:
public class ToggleableWrapper<T> : INotifyPropertyChanged {
private bool toggled;
public ToggleableWrapper(T item){
this.Item = item;
this.ClickCommand = new RelayCommand(() => this.Toggled = !this.Toggled);
}
public T Item {get;}
public ICommand ClickCommand {get;}
public bool Toggled {
get { return this.toggled; }
set {
this.toggled = value;
OnPropertyChanged(nameof(this.Toggled));
}
}
//Property changed implementation...
}
So your NumberOfItems collection could look like this:
public ObservableCollection<ToggleableWrapper<int>> NumberOfItems {get;}
Now you need a ValueConverter which will convert the toggled boolean to your image. Call it ToggledToImageConverter
You can implement it accordingly and make it a resource somewhere.
Now your ListView looks like this:
<ListView ItemsSource="{Binding NumberOfItems}" SelectedItem="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<Button Name="test" Grid.Row="0" Grid.Column="10" Grid.ColumnSpan="4" Grid.RowSpan="4" VerticalAlignment="Center" Background="Transparent" Command="{Binding ClickCommand}">
<Button.Template>
<ControlTemplate>
<Grid RenderTransformOrigin="0.5,0.5" x:Name="bg">
<Image Source="{Binding Toggled, Converter={StaticResouce ToggledToImageConverter}"/>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
<DataTemplate>
</ListView.ItemTemplate>
</ListView>
So when you click the button, the bool is toggled, which will then toggle the image using the ValueConverter.

ListBox with WrapPanel - Remove item / arrow key navigation

I have a strange problem regarding my listbox / wrappanel arrow navigation.
My Listbox :
<ListBox x:Name="List"
IsSynchronizedWithCurrentItem="True"
SelectedIndex="{Binding MainIndex, Mode=OneWayToSource}"
SelectedItem="{Binding CurrentFeed, Mode=TwoWay}"
ItemsSource="{Binding CurrentFeedList}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" MaxWidth="{Binding ActualWidth, ElementName=Panel}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="168">
<Border BorderBrush="White" BorderThickness="0" Margin="0,7,0,0">
<Image Source="{Binding Image , Converter={StaticResource ByteArraytoImageSource}}" Width="150" Height="213" ></Image>
</Border>
<Border BorderBrush="White" BorderThickness="0" Height="65" HorizontalAlignment="Center" >
<TextBlock VerticalAlignment="Center" TextWrapping="Wrap" FontFamily="{StaticResource DefaultFontFamily}" FontSize="15" Text="{Binding Title}" Foreground="White" Cursor="Hand" HorizontalAlignment="Center" TextAlignment="Center"/>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The render :
(source: free.fr)
The render is great No problem ! ;)
As you can see it's a list of wallpaper retrieved from RSS.
I have bind a shortcut to make one item readed and then it's removed from the ObservableCollection :
<UserControl.InputBindings>
<KeyBinding Key="D" Modifiers="Control" Command="{Binding ReadCmd}" />
</UserControl.InputBindings>
In My View Model :
ObservableCollection<Feed> CurrentFeedList { get; set; }
private Feed _currentFeed;
public Feed CurrentFeed
{
get { return _currentFeed; }
set
{
_currentFeed = value;
OnPropertyChanged("CurrentFeed");
}
}
public ICommand ReadCmd { get; set; }
public int MainIndex { get; set; }
My ReadCmd is a RelayCommand that call "ReadAction" method.
At the beginning I simply remove the item from the ObservableCollection, but I want to type Ctrl+D twice to read 2 item.
So I decided to get the index of the listbox and select back the same index after removing one.
private void ReadAction()
{
int previousIndex = MainIndex;
if (previousIndex == CurrentFeedList.Count - 1)
previousIndex -= 1;
CurrentFeedList.Remove(CurrentFeed);
CurrentFeed = CurrentFeedList[previousIndex];
}
It's work as I wanted, but one problem remain and I can't solve it.
When I push Ctrl+D the SelectedItem is remove and the next item become Selected. But then when I use the arrow key navigation to navigate over the list, it jump each time to the first item of the list.
I hope it's enough clear.
Thanks you.
This issue is due to the fact that when you are removing the items using the KeyBinding, ListBox loses the focus and hence pressing the arrow keys after that places focus on first item of the ListBox.
So in order to make your arrow keys work from the selected item you will also have to set the focus on Selected Item of the Listbox.
There are couple of ways of doing this, e.g using attached behavior to set the focus on the ListBoxItem. Simplest would be to capture the SelectionChanged event of your ListBox and in the handler set the focus on the currently selected item like below:
private void MyListBox_OnSelectionChanged(object sender, RoutedEventArgs e)
{
var item = (ListBoxItem)MyListBox.ItemContainerGenerator.ContainerFromItem(MyListBox.SelectedItem);
if(item !=null)
item.Focus();
}

TabControl SelectedItem Binding Problems

In a Tabbed interface application i'm trying to pass the TabItem Header string or the object contained in the selected TabItem to the view model to use it to
publish an event like this:
In the View (xaml):
<TabControl x:Name="MyTC"
prism:RegionManager.RegionName="{x:Static inf:RegionNames.MainRegion}"
SelectedItem="{Binding Path=TabControlSelectedItem,UpdateSourceTrigger=PropertyChanged,Mode=Twoway}"
Cursor="Hand"
Grid.Row="0"
Grid.Column="1">
<TabControl.ItemTemplate>
<DataTemplate>
<!--DataContext="{Binding ElementName=MyTC, Path=SelectedItem}"-->
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Margin="3"
Text="{Binding Path=DataContext.DataContext.HeaderInfo, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabItem}}}"
/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding HeaderClickCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
In View Model
//***********************************************************
//Constructor
public ShellWindowViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
this.HeaderClickCommand = new DelegateCommand(OnHeaderClick);
}
//SelectedItem Binding
private object tabControlSelectedItem;
public object TabControlSelectedItem
{
get { return tabControlSelectedItem; }
set
{
if (tabControlSelectedItem != value)
{
tabControlSelectedItem = value;
OnPropertyChanged("TabControlSelectedItem");
}
}
}
//*****************************************************************************************************
//this handler publish the Payload "SelectedSubsystem" for whoever subscribe to this event
private void OnHeaderClick()
{
//EA for communication between Modules not within Modules
string TabHeader = (TabControlSelectedItem as TabItem).Header.ToString();
eventAggregator.GetEvent<SubsystemIDSelectedEvent>().Publish(TabHeader);
}
but there is something wrong because when i click the TabItem nothing happen and when i inserted a breakpoint # TabControlSelectedItem
property i found it contain the view namespace.i want the TabControlSelectedItem to get the selected Tab header string or the object in
the selected tab item.
Your help is very much appreciated.
I just tried something similar and it works fine.
Here is my xaml
<TabControl ItemsSource="{Binding Matches}"
SelectedItem="{Binding SelectedRecipe}">
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Path=Date}" />
<TextBlock Grid.Column="1"
TextAlignment="Center"
Text="{Binding Path=Name}" />
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
And then in my viewmodel
public object SelectedRecipe
{
get
{
return _selectedRecipe;
}
set
{
_selectedRecipe = value;
OnNotifyPropertyChanged("SelectedRecipe");
}
}

ObservableCollection Images in Listbox to Content Control master detail WPf

I have an observablecollection of Images that get populated via the following code:
<StackPanel Orientation="Horizontal" Grid.Column="0">
<ListBox ItemsSource="{Binding BigImageView}" IsSynchronizedWithCurrentItem="True"
SelectedIndex="0" SelectedItem="{Binding CurrentItem}" />
</StackPanel>
<ContentControl Name="Detail" Content="{Binding BigImageView, Mode=OneWay}"
Margin="9,0,0,0" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top"/>
However the Content Control is supposed to bind to the BigImageView via an ObservableCollection
BigImage = new ObservableCollection<Image>();
_listView = CollectionViewSource.GetDefaultView(BigImage);
_listView.CurrentChanged += new EventHandler(OnCurrentChanged);
public System.ComponentModel.ICollectionView BigImageView
{
get
{
return _listView;
}
set
{
_listView = value;
OnPropertyChanged("BigImageView");
}
}
I want to return the image to the content control when I move the listbox. I have been racking my brain and trying everyhitn but it does not work. any help would be appreciated.
There is no need to bind the selecteditem, the collectionview should take care of that.
Try this:
<ListBox ItemsSource="{Binding BigImageView}" IsSynchronizedWithCurrentItem="True" />
<ContentControl Name="Detail" Content="{Binding BigImageView, Mode=OneWay}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
<ContentControl.ContentTemplate>
1
Create a viewmodel with a list and a selected item:
public class BigImageViewModel : INotifyPropertyChanged
{
private string bigImage;
//string for path?
public ObservableCollection<string> BigImageView {get; set; } //Of course, make sure it has a value
public string SelectedBigImage
{
get { return bigImage; }
set { bigImage = values; NotifyPropertyChanged("SelectedBigImage"); }
}
}
Set this object on the DataContext of your control in the constructor:
DataContext = new BigImage(); //Make sure you initialize your list
Set the ListBox ItemsSource to your BigImage list, bind your SelectedItem to BigImageView
and use that in your content control:
<ListBox ItemsSource="{Binding BigImageView}" SelectedItem={Binding SelectedBigImage} />
ContentControl:
<ContentControl Name="Detail" Content="{Binding SelectedBigImage, Mode=OneWay}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/> <!-- Nice template for showing your string BigImage -->
</DataTemplate>
<ContentControl.ContentTemplate>
</ContentControl>
2
Or screw that view model:
Set the list directly in the constructor (after the InitializeComponent() ):
myListBox.ItemsSource = ObservableCollection<string>(); //Make sure you initialize your list with whatever your object is..
Give the list a name:
And bind with an ElementName binding to your selected item:
<ContentControl Name="Detail" Content="{Binding ElementName=myListBox, Path=SelectedItem}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/> <!-- Nice template for showing your string BigImage -->
</DataTemplate>
<ContentControl.ContentTemplate>
</ContentControl>

Resources