In the following xaml code, I'm trying bind a RelayCommand ResourceButtonClick, which is in the view model. In addition to that, I want to pass the Resource.Id as a parameter to this command.
However, ResourceButtonClick is not called. I suspect that by setting the ItemsSource to Resources, I override the data context, which was view model.
<UserControl ...>
<Grid>
<ItemsControl ItemsSource="{Binding Resources}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Tag="{Binding Id}" Content="{Binding Content}"
Width="300" Height="50"
Command="{Binding ResourceButtonClick}"
CommandParameter="{Binding Id}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Here's the RelayCommand in the view model.
public RelayCommand<int> ResourceButtonClick { get; private set; }
The constructor of the view model:
public ResourcesViewModel()
{
this.ResourceButtonClick =
new RelayCommand<int>((e) => this.OnResourceButtonClick(e));
}
The method in the view model:
private void OnResourceButtonClick(int suggestionId)
{
...
}
I have two questions: First, how can I call the ResourceButtonClick command. Second, how can I pass Resource.Id as a parameter to that command.
Any suggestion will be appreciated.
Maybe you can show your complete ViewModel class? Assuming, that the ResourceButtonClick command is on the ViewModel which also holds the collection Resources, you're trying to access the command on the wrong object (on the item in Resources, instead of the ViewModel which holds the Resources collection and the command).
Therefore you would have to access the command on the 'other' DataContext, this is the DataContext of the ItemsControl not of it's item. The easiest way is to use the ElementName attribute of the binding:
<UserControl ...>
<Grid>
<ItemsControl ItemsSource="{Binding Resources}" Name="ResourcesItemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Tag="{Binding Id}" Content="{Binding Content}"
Width="300" Height="50"
Command="{Binding DataContext.ResourceButtonClick, ElementName=ResourcesItemsControl}"
CommandParameter="{Binding Id}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Maybe this solves the problem, otherwise please let me know and provide some more detail.
I usually pass the whole item as a CommandParameter, not an ID. This doesn't cost anything and you don't have to translate the ID back to the item. But that depends on your case.
Hope this helps.
I resolved this in a such way:
In View.xaml
1) I added a property SelectedItem for my ListView:
<ListView Name="MyList" ItemsSource="{Binding MyList}" SelectedItem="{Binding MySelectedItem, Mode=TwoWay}" >
2) I added a Command property to a button:
In viewModel:
3) I added a command handler:
MyCommand = new RelayCommand(MyMethod);
4) I added method MyMethod which takes value(s) from MySelectedItem property:
private void MyMethod ()
{
MyType mt = MySelectedItem;
//now you have access to all properties of your item via mt object
}
Related
I'm trying to develop library of user controls that arranges multiple UIElements in specific way. I use ItemControl to show list of UIElements. I want to surround every item from item control with Stack.
I would like to use my library more or less this manner.
<pcLayouts:ListLayout>
<pcLayouts:ListLayout.ParentItems>
<TextBlock Width="145">1</TextBlock>
<TextBlock>2</TextBlock>
<TextBlock>3</TextBlock>
</pcLayouts:ListLayout.ParentItems>
</pcLayouts:ListLayout>
I declared dependency property in backing class ListLayout cs and xaml files.
public static readonly DependencyProperty ParentItemsProperty = DependencyProperty.Register(
"ParentItems", typeof(ObservableCollection<UIElement>), typeof(ColumnLayout),
new PropertyMetadata(new ObservableCollection<UIElement>()));
...
public ObservableCollection<UIElement> ParentItems
{
get { return (ObservableCollection<UIElement>)GetValue(ParentItemsProperty); }
set
{
SetValue(ParentItemsProperty, value);
OnPropertyChanged();
}
}
<StackPanel x:Name="MainPanel" Orientation="Vertical">
<ItemsControl ItemsSource="{Binding ParentItems, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<WHAT SHOULD I PUT HERE??/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
It seems DataTemplate isn't used at all when binding to Binding ParentItems, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}. How can I use this data template or is there another way?
this is because ItemsControl.IsItemItsOwnContainerOverride returns true for UIElement. Normally a ContentPresenter is used which generates the DataTemplate.
If you insist on using DataTemplate you create a new class derived from ItemsControl and override IsItemItsOwnContainerOverride to return false.
I have a class defined this way:
public class CustomClass
{
string Name;
int Age;
Usercontrol usercontrol;
}
where Usercontrol is a visual element that I want to insert in a WrapPanel.
CustomClass is organized in a static ObservableCollection.
public static class CollectionClass
{
public static ObservableCollection<CustomClass> MyCollection = new ObservableCollection<CustomClass>();
}
I am looking to bind the usercontrol property of the CustomClass in the collection to be visualized in the WrapPanel, so I have the visual elements showed in the same order as the elements in the collection.
Right now I am populating the WrapPanel manually by code, but I figured that there has to be a way to do it quickly and easily through databinding.
I am trying to do it with a ItemsControl defined this way:
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
but I don't know how to make the magic happen.
EDIT:
I tried the solution proposed by ChrisO, implemented like that:
1 - I made the collection a property
2 - I made the UserControl a property
3 - I set DataContex from code:
SensorMenuWrap.Datacontext = CollectionClass.MyCollection
4 - The binding:
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding usercontrol}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now I can visualize the first element of the collection. If the first element changes, I visualize the new first element. How can I visualize the entire collection?
I haven't tested this. Also, make sure that userControl is a property rather than a field. I'm assuming you know how to set the DataContext up correctly to refer to the MyCollection property.
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding userControl}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You should also consider referring directly to the UserControl in the DataTemplate rather than binding to it as a property on the class. That would look like this
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit:
In response to your edit, you would be much better tackling this with a reusable UserControl with DataBindings on the properties of CustomClass. Here's what I have to achieve that:
CollectionClass
public static class CollectionClass
{
public static ObservableCollection<CustomClass> MyCollection { get; set; }
static CollectionClass()
{
MyCollection = new ObservableCollection<CustomClass>();
MyCollection.Add(new CustomClass { Age = 25, Name = "Hamma"});
MyCollection.Add(new CustomClass { Age = 32, Name = "ChrisO"});
}
}
CustomClass
public class CustomClass
{
public string Name { get; set; }
public int Age { get; set; }
}
UserControl contents
<StackPanel>
<TextBlock Background="Tan" Text="{Binding Name}" />
<TextBlock Background="Tan" Text="{Binding Age}" />
</StackPanel>
MainWindow
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<so25728310:Usercontrol Margin="5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here, instead of having the Usercontrol baked into your data, you're keeping it in the view. It's always good to have a separation between your view and data. You could even do away with the Usercontrol and have the two TextBoxes directly in the DataTemplate but it depends on whether you would want to reuse that view elsewhere.
I want to make horizontal ListBox with customized item template, so I make a basic template of it.
However, I couldn't find an example of binding 'things' to that WPF XAML, especially with ListBox filled with customized items.
I simply want to dynamically add/remove items in the ListBox with Image, Label, ComboBox with previously filled with number 1 to 10.
the ADD/REMOVE button will be placed outside WPF control, it means that the buttons will be on the Main Window Form.
Also, there are TextBox and picture selector in the Main Window Form so that I can change the text and image.
Below is code behind XAML :
Public Class listSequence
Public Sub New()
InitializeComponent()
listbox.Items.Add("hi")
listbox.Items.Add("there")
End Sub
End Class
Below is XAML :
<ListBox Name="listbox" VerticalContentAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Padding="10" Margin="5" BorderThickness="1" BorderBrush="Aqua" CornerRadius="0" Width="120" VerticalAlignment="Stretch">
<StackPanel>
<Image />
<Label Content="{Binding}" />
<TextBlock Text="hi" />
<ComboBox x:Name="cboRepeat" ItemsSource="{Binding}" DisplayMemberPath="RepeatCounter" IsSynchronizedWithCurrentItem="True"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Let's say we have a basic class named Item:
public class Item : INotifyPropertyChanged
{
public string Text { get; set; } // Implement INotifyPropertyChanged
public string ImagePath { get; set; } // properly on these properties
}
And a collection of these in a view model:
public ObservableCollection<Item> Items { get; set; }
Now to display these items in the UI, we use a ListBox and set the ItemsSource property:
<ListBox ItemsSource="{Binding Items}">
</ListBox>
When it comes to defining the ListBox.ItemTemplate, you need to understand that this DataTemplate will be applied to each item and that it has access to all of the properties defined in the Item class:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image ImageSource="{Binding ImagePath}" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Therefore, you can access the properties in the collection class as shown above. You can find out the full story by looking at the ItemsControl.ItemTemplate Property page on MSDN.
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>
I have a WPF ListView with a collection of RadioButtons. I want to set the GroupName of the child controls to be bound to a property on the parent data context. At the moment I am doing this by duplicating the property in each of the children's data context but that can't be right.
My XAML:
<ListView ItemsSource="{Binding OptionItems}" >
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type Logging:FilterOptionsRadioListViewModel}">
<RadioButton GroupName="{Binding GroupName}" Content="{Binding Option.Value}" Tag="{Binding Option.Key}" IsChecked="{Binding IsChecked}" Command="Logging:FilterOptionsRadioListViewModel.CheckedChangedCommand" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
GroupName is a property on the parent View Model. I currently pass this onto the child View Model (where it is also a property) in the child constructor:
var item = new FilterOptionsRadioListItemViewModel(option, this.GroupName);
What is the correct way of doing this?
<ListView ItemsSource="{Binding OptionItems}" >
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type Logging:FilterOptionsRadioListViewModel}">
<RadioButton GroupName="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.GroupName}" Content="{Binding Option.Value}" Tag="{Binding Option.Key}" IsChecked="{Binding IsChecked}" Command="Logging:FilterOptionsRadioListViewModel.CheckedChangedCommand" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You certainly can do that, but if you don't want to have to copy the GroupName to every child Option instance, you could instead do something like the following
// I'm imagining your viewmodels look something like this
public class ParentViewModel
{
private ObservableCollection<Option> m_OptionItems;
public ObservableCollection<Option> OptionItems
{
get { return m_OptionItems; }
set { m_OptionItems = value; }
}
private string m_ParentGroupName;
public string ParentGroupName
{
get { return m_ParentGroupName; }
set
{
m_ParentGroupName = value;
}
}
}
public class Option
{
private string m_Value;
public string Value
{
get { return m_Value; }
set
{
m_Value = value;
}
}
}
Then you can use a relative binding so you can look up the control tree to find the right datacontext, so you don't have to copy the ParentGroupName to each child Option instance.
If you name your window, you can traverse the root DataContext.
<Window x:Class="MyNamespace.MainWindow"
Name="Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="500" Width="725">
<ListView ItemsSource="{Binding OptionItems}" >
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type Logging:FilterOptionsRadioListViewModel}">
<RadioButton GroupName="{Binding ElementName=Window, Path=DataContext.ParentGroupName}" Content="{Binding Option.Value}" Tag="{Binding Option.Key}" IsChecked="{Binding IsChecked}" Command="Logging:FilterOptionsRadioListViewModel.CheckedChangedCommand" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>