setting datacontext for a view in a tab item - c#

in my window resources i have a datatemplate for my viewmodel and a static mainviewmodel
<Window.Resources>
<DataTemplate DataType="{x:Type SharedViewModels:DatabaseViewModel}">
<SharedViews:DatabaseView/>
</DataTemplate>
<LocalViewModels:SharedSettingsViewModel x:Key="SharedSettingsViewModel"/>
</Window.Resources>
DatabaseViewModel is an instance in my static SharedSettingsViewModel
and i have this tab control
<TabControl DataContext="{Binding Source={StaticResource SharedSettingsViewModel}}" ItemsSource="{Binding SharedSettingsViewModelsTabList}">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Content}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
and i add items to my TabControl itemssorce like this
SharedSettingsViewModelTabList.Add(new Shared.Models.TabItem() { Header = "Sql Databases", Content = DatabaseViewModel });
How do set the datacontext of my DatabaseView to the DatabaseViewModel instance that is inside my SharedSettingsViewModel?
in my view DatabaseView XAML which is a user control i tried
DataContext="{Binding DatabaseViewModel}"
but this doesnt seem to work
here is my TabItem Model
public class TabItem : MVVM.ObservableObject
{
public string Header { get; set; }
public MVVM.ObservableObject Content { get; set; }
}
my DatabaseViewModel which will be defined as a Property in SharedSettingsViewModel
private ObservableCollection<Models.Database> databases;
public ObservableCollection<Models.Database> Databases
{
get
{
if (databases == null)
{
databases = new ObservableCollection<Models.Database>();
databases.Add(new Models.Database() { Displayname = "new" });
}
return databases;
}
set
{
SetField(ref databases, value, "Databases");
}
}
and here is my SharedSettingsViewModel which contains the tab items collection
public class SharedSettingsViewModel : MVVM.ObservableObject
{
private ObservableCollection<Shared.Models.TabItem> SharedSettingsViewModelTabList;
public ObservableCollection<Shared.Models.TabItem> SharedSettingsViewModelTabList
{
get
{
if (sharedSettingsViewModelTabList == null)
{
sharedSettingsViewModelTabList = new ObservableCollection<Shared.Models.TabItem>();
}
return sharedSettingsViewModelTabList;
}
}
//my DatabaseViewModel property..as a child view model
private Shared.ViewModels.DatabaseViewModel databaseViewModel;
public Shared.ViewModels.DatabaseViewModel DatabaseViewModel
{
get
{
if (databaseViewModel == null)
{
databaseViewModel = new Shared.ViewModels.DatabaseViewModel();
}
return databaseViewModel;
}
set
{
SetField(ref databaseViewModel, value, "DatabaseViewModel");
}
}
}

You will have to update the code as below and your DatabaseViewModel will become the DataContext of your DatabaseView which will be the DataTemplate for the Content of ContentPresenter in ContentTemplate of your TabControl
<TabControlDataContext="{Binding Source={StaticResource SharedSettingsViewModel}}" ItemsSource="{Binding SharedSettingsViewModelsTabList}">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Content}"/>
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>

Related

Navigate through TabItem using MVVm

Assum that I have 3 user Control(TIShowNames,TIEnterCode,TIShowFactor).
they have their views and their corresponding viewModel.
all these 3, are in mainwindowView.
Here is my mainwindowView Xaml:
<Controls:TransitionPresenter Name="transContainer" Grid.Row="2" RestDuration="0:0:1" IsLooped="False" Transition="{StaticResource SlideTransition}">
<TabControl Name="TCMain" Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">
<TabItem Name="TIShowNames" Visibility="Collapsed">
<views:NameView x:Name="NameViewElement" />
</TabItem>
<TabItem Name="TIEnterCode" Visibility="Collapsed">
<views:CodeView x:Name="CodeViewElement" />
</TabItem>
<TabItem Name="TIShowFactor" Visibility="Collapsed">
<views:FactorDetailView x:Name="FactorDetailViewElement" />
</TabItem>
</TabControl>
</Controls:TransitionPresenter>
In my old Programming style i used to use this line of code for navigating through tab items(without any pattern):
private void ChangeTabItemTo(TabItem TI)
{
transContainer.ApplyTransition("TCMain", "TCMain");
TCMain.SelectedItem = TI;
}
I have a btn show in "TIShowNames", so when i clicks on that it has to go to "TIShowFactor".
In MVVM, ViewModel does not know any thing about view(this item tab is in its parent view!!!). so how he can change selected Tab Item without violating MVVM??
Another Try:
Changing Selectedindex wont work because of this error:
"System.Windows.Data Error: 40 : BindingExpression path error: 'Index'
property not found on 'object' ''MainWindowViewModel'
(HashCode=22018304)'. BindingExpression:Path=AAA;
DataItem='MainWindowViewModel' (HashCode=22018304); target element is
'TabControl' (Name=''); target property is 'IsSelected' (type
'Boolean')"
Update:
Controls:TransitionPresenter is from Fluid DLL
Update:
I want to hide tab item's header so no one can click the header and navigatoin through header is possibe only via btns in usercontrols
You could define a DataTemplate per view model type in the view:
<TabControl Name="TCMain"
ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding ViewModel}"
Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:NameViewViewModel}">
<views:NameView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:CodeViewViewModel}">
<views:CodeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:FactorDetailViewModel}">
<views:FactorDetailView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
...and bind the SelectedItem property to a source property that you set in your view model, e.g.:
public object ViewModel
{
get { return _vm; }
set { _vm = value; NotifyPropertyChanged(); }
}
...
ViewModel = new CodeViewViewModel(); //displays the CodeView
Expanding on mm8's answer, this is how I'd do it:
First of all, I would create a BaseViewModel class to be inherited by every view model that will represent each tab of the TabControl.
I like to implement it as an abstract class with an abstract string property called "Title", so I can dynamically create the tabs and display their names (or titles). This class would also implement the NotifyPropertyChanged interface.
public abstract class BaseViewModel : INotifyPropertyChanged
{
public abstract string Title { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then I would create each view model inheriting from this base view model. for example:
public class NameViewModel : BaseViewModel
{
public override string Title
{
get
{
return "Name";
}
}
}
You would do the same for the other view models, only changing the "title" property of each of them.
Now I would create the MainView of the application and its corresponding view model.
The MainViewModel would have a collection of BaseViewModels and a "CurrentViewModel" (of type BaseViewModel) and would add all the view models you want to its collection on its constructor, like this:
public class MainViewModel : BaseViewModel
{
public override string Title
{
get
{
return "Main";
}
}
private ObservableCollection<BaseViewModel> _viewModels;
public ObservableCollection<BaseViewModel> ViewModels
{
get { return _viewModels; }
set
{
if (value != _viewModels)
{
_viewModels = value;
OnPropertyChanged();
}
}
}
private BaseViewModel _currentViewModel;
public BaseViewModel CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (value != _currentViewModel)
{
_currentViewModel = value;
OnPropertyChanged();
}
}
}
public MainViewModel()
{
ViewModels = new ObservableCollection<BaseViewModel>();
ViewModels.Add(new NameViewModel());
ViewModels.Add(new CodeViewModel());
ViewModels.Add(new FactorDetailViewModel());
}
}
Finally, your main view would be similar to what mm8 posted:
(Notice the differences from my code to mm8's code: (1) You need to set the DisplayMemberPath of the TabControl to the "Title" property of the BaseViewModels and (2) You need to set the DataContext of the Window to your MainViewModel)
<Window ...>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<TabControl Name="TCMain"
ItemsSource="{Binding ViewModels}"
DisplayMemberPath="Title"
SelectedItem="{Binding CurrentViewModel}"
Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:NameViewModel}">
<local:NameView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:CodeViewModel}">
<local:CodeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:FactorDetailViewModel}">
<local:FactorDetailView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Now it should work as expected. Everytime you change the active tab of the TabControl, the SelectedItem property of the control will change to the corresponding view model, which will be templated as its corresponding view.
This approach is called "View Model First" (instead of View First), by the way.
EDIT
If you want to have a button on one of the view models that has a command to change the current view model, this is how you do it:
I suppose you are familiarized with Josh Smith's RelayCommand. If you are not, just search for its implementation on the web.
You will need to create an ICommand property on your MainViewModel, which will be responsible to change the "CurrentViewModel" property:
private ICommand _showFactorDetailCommand;
public ICommand ShowFactorDetailCommand
{
get
{
if (_showFactorDetailCommand == null)
{
_showFactorDetailCommand = new RelayCommand(p => true, p => show());
}
return _showFactorDetailCommand;
}
}
private void show()
{
CurrentViewModel = ViewModels.Single(s => s.Title == "Factor");
}
The show() method above simply searches the collection of view models that has the title "Factor" and set it to the CurrentViewModel, which in turn will be the Content of the ContentControl that acts as the ContentTemplate of your TabControl inside your main view.
Remember that your FactorDetailViewModel should be implemented as follows:
public class FactorDetailViewModel : ViewModelBase
{
public override string Title
{
get
{
return "Factor";
}
}
}
The button inside your "NameView" will bind to this command which is a property of "MainViewModel" using RelativeSource binding:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowFactorDetailCommand}" Content="Show Factor" Height="20" Width="60"/>
You could make this command more generic, passing the title of the view model you would like to navigate to as the command parameter:
private ICommand _showCommand;
public ICommand ShowCommand
{
get
{
if (_showCommand == null)
{
_showCommand = new RelayCommand(p => true, p => show(p));
}
return _showCommand;
}
}
private void show(p)
{
var vm = (string)p;
CurrentViewModel = ViewModels.Single(s => s.Title == vm);
}
Then on your views, pass the Command Parameter too:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowCommand}" Content="Show Factor" CommandParameter="Factor" Height="20" Width="60"/>
Finally, to hide your TabItems completely, you need to set the ItemContainerStyle of your TabControl so that the Visibility of your TabItems has the value of "Collapsed".
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</TabControl.ItemContainerStyle>

How to set a Binding from ItemTemplate to the hosting Container in an ItemsControl? (UWP)

Given an arbitrary ItemsControl, like a ListView, I want to set a Binding from inside the ItemsTemplate to the hosting Container. How can I do that easily? For example, in WPF we can do it using this inside the ItemTemplate
<ListView.ItemTemplate>
<DataTemplate>
<SomeControl Property="{Binding Path=TargetProperty, RelativeSouce={RelativeSource FindAncestor, AncestorType={x:Type MyContainer}}}" />
</DataTemplate>
<ListView.ItemTemplate>
In this example (for WPF) the Binding will be set between Property in SomeControl and TargetProperty of the ListViewItem (implicit, because it will be generated dynamically by the ListView to host the each of its items).
How can we do achieve the same in UWP?
I want something that is MVVM-friendly. Maybe with attached properties or an Interaction Behavior.
When the selection changes, search the visual tree for the radio button with the DataContext corresponding to selected/deselected items. Once it's found, you can check/uncheck at your leisure.
I have a toy model object looking like this:
public class Data
{
public string Name { get; set; }
}
My Page is named self and contains this collection property:
public Data[] Data { get; set; } =
{
new Data { Name = "One" },
new Data { Name = "Two" },
new Data { Name = "Three" },
};
The list view, binding to the above collection:
<ListView
ItemsSource="{Binding Data, ElementName=self}"
SelectionChanged="OnSelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The SelectionChanged event handler:
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView lv = sender as ListView;
var removed = FindRadioButtonWithDataContext(lv, e.RemovedItems.FirstOrDefault());
if (removed != null)
{
removed.IsChecked = false;
}
var added = FindRadioButtonWithDataContext(lv, e.AddedItems.FirstOrDefault());
if (added != null)
{
added.IsChecked = true;
}
}
Finding the radio button with a DataContext matching our Data instance:
public static RadioButton FindRadioButtonWithDataContext(
DependencyObject parent,
object data)
{
if (parent != null)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
ListViewItem lv = child as ListViewItem;
if (lv != null)
{
RadioButton rb = FindVisualChild<RadioButton>(child);
if (rb?.DataContext == data)
{
return rb;
}
}
RadioButton childOfChild = FindRadioButtonWithDataContext(child, data);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
And finally, a helper method to find a child of a specific type:
public static T FindVisualChild<T>(
DependencyObject parent)
where T : DependencyObject
{
if (parent != null)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
T candidate = child as T;
if (candidate != null)
{
return candidate;
}
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return default(T);
}
The result:
This will break if a given model instance shows up more than once in the list.
Note: this answer is based on WPF, there might be some changes necessary for UWP.
There are basically two cases to consider:
You have a data driven aspect that needs to be bound to the item container
You have a view-only property
Lets assume a customized listview for both cases:
public class MyListView : ListView
{
protected override DependencyObject GetContainerForItemOverride()
{
return new DesignerItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is DesignerItem;
}
}
public class DesignerItem : ListViewItem
{
public bool IsEditing
{
get { return (bool)GetValue(IsEditingProperty); }
set { SetValue(IsEditingProperty, value); }
}
public static readonly DependencyProperty IsEditingProperty =
DependencyProperty.Register("IsEditing", typeof(bool), typeof(DesignerItem));
}
In case 1, you can use the ItemContainerStyle to link your viewmodel property with a binding and then bind the same property inside the datatemplate
class MyData
{
public bool IsEditing { get; set; } // also need to implement INotifyPropertyChanged here!
}
XAML:
<local:MyListView ItemsSource="{Binding Source={StaticResource items}}">
<local:MyListView.ItemContainerStyle>
<Style TargetType="{x:Type local:DesignerItem}">
<Setter Property="IsEditing" Value="{Binding IsEditing,Mode=TwoWay}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</local:MyListView.ItemContainerStyle>
<local:MyListView.ItemTemplate>
<DataTemplate>
<Border Background="Red" Margin="5" Padding="5">
<CheckBox IsChecked="{Binding IsEditing}"/>
</Border>
</DataTemplate>
</local:MyListView.ItemTemplate>
</local:MyListView>
In case 2, it appears that you don't really have a data driven property and consequently, the effects of your property should be reflected within the control (ControlTemplate).
In the following example a toolbar is made visible based on the IsEditing property. A togglebutton can be used to control the property, the ItemTemplate is used as an inner element next to the toolbar and button, it knows nothing of the IsEditing state:
<local:MyListView ItemsSource="{Binding Source={StaticResource items}}">
<local:MyListView.ItemContainerStyle>
<Style TargetType="{x:Type local:DesignerItem}">
<Setter Property="IsEditing" Value="{Binding IsEditing,Mode=TwoWay}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DesignerItem}">
<DockPanel>
<ToggleButton DockPanel.Dock="Right" Margin="5" VerticalAlignment="Top" IsChecked="{Binding IsEditing,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" Content="Edit"/>
<!--Toolbar is something control related, rather than data related-->
<ToolBar x:Name="MyToolBar" DockPanel.Dock="Top" Visibility="Collapsed">
<Button Content="Tool"/>
</ToolBar>
<ContentPresenter ContentSource="Content"/>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsEditing" Value="True">
<Setter TargetName="MyToolBar" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:MyListView.ItemContainerStyle>
<local:MyListView.ItemTemplate>
<DataTemplate>
<Border Background="Red" Margin="5" Padding="5">
<TextBlock Text="Hello World"/>
</Border>
</DataTemplate>
</local:MyListView.ItemTemplate>
</local:MyListView>
For a better control template, you may chose to use Blend and create the control template starting at the full ListViewItem template and just editing your changes into it.
If your DesignerItem generally has a specific enhanced appearance, consider designing it in the Themes/Generic.xaml with the appropriate default style.
As commented, you could provide a separate data template for the editing mode. To do this, add a property to MyListView and to DesignerItem and use MyListView.PrepareContainerForItemOverride(...) to transfer the template.
In order to apply the template without the need for Setter.Value bindings, you can use value coercion on DesignerItem.ContentTemplate based on IsEditing.
public class MyListView : ListView
{
protected override DependencyObject GetContainerForItemOverride()
{
return new DesignerItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is DesignerItem;
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var elem = element as DesignerItem;
elem.ContentEditTemplate = ItemEditTemplate;
}
public DataTemplate ItemEditTemplate
{
get { return (DataTemplate)GetValue(ItemEditTemplateProperty); }
set { SetValue(ItemEditTemplateProperty, value); }
}
public static readonly DependencyProperty ItemEditTemplateProperty =
DependencyProperty.Register("ItemEditTemplate", typeof(DataTemplate), typeof(MyListView));
}
public class DesignerItem : ListViewItem
{
static DesignerItem()
{
ContentTemplateProperty.OverrideMetadata(typeof(DesignerItem), new FrameworkPropertyMetadata(
null, new CoerceValueCallback(CoerceContentTemplate)));
}
private static object CoerceContentTemplate(DependencyObject d, object baseValue)
{
var self = d as DesignerItem;
if (self != null && self.IsEditing)
{
return self.ContentEditTemplate;
}
return baseValue;
}
private static void OnIsEditingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(ContentTemplateProperty);
}
public bool IsEditing
{
get { return (bool)GetValue(IsEditingProperty); }
set { SetValue(IsEditingProperty, value); }
}
public static readonly DependencyProperty IsEditingProperty =
DependencyProperty.Register("IsEditing", typeof(bool), typeof(DesignerItem), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsEditingChanged)));
public DataTemplate ContentEditTemplate
{
get { return (DataTemplate)GetValue(ContentEditTemplateProperty); }
set { SetValue(ContentEditTemplateProperty, value); }
}
// Using a DependencyProperty as the backing store for ContentEditTemplate. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContentEditTemplateProperty =
DependencyProperty.Register("ContentEditTemplate", typeof(DataTemplate), typeof(DesignerItem));
}
Note, for an easier example I will activate the "edit" mode by ListViewItem.IsSelected with some trigger:
<local:MyListView ItemsSource="{Binding Source={StaticResource items}}">
<local:MyListView.ItemContainerStyle>
<Style TargetType="{x:Type local:DesignerItem}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="IsEditing" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</local:MyListView.ItemContainerStyle>
<local:MyListView.ItemTemplate>
<DataTemplate>
<Border Background="Red" Margin="5" Padding="5">
<TextBlock Text="Hello World"/>
</Border>
</DataTemplate>
</local:MyListView.ItemTemplate>
<local:MyListView.ItemEditTemplate>
<DataTemplate>
<Border Background="Green" Margin="5" Padding="5">
<TextBlock Text="Hello World"/>
</Border>
</DataTemplate>
</local:MyListView.ItemEditTemplate>
</local:MyListView>
Intended behavior: the selected item becomes edit-enabled, getting the local:MyListView.ItemEditTemplate (green) instead of the default template (red)
Just in case you might want to have an IsSelected property in your view model item class, you may create a derived ListView that establishes a Binding of its ListViewItems to the view model property:
public class MyListView : ListView
{
public string ItemIsSelectedPropertyName { get; set; } = "IsSelected";
protected override void PrepareContainerForItemOverride(
DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
BindingOperations.SetBinding(element,
ListViewItem.IsSelectedProperty,
new Binding
{
Path = new PropertyPath(ItemIsSelectedPropertyName),
Source = item,
Mode = BindingMode.TwoWay
});
}
}
You might now simply bind the RadioButton's IsChecked property in the ListView's ItemTemplate to the same view model property:
<local:MyListView ItemsSource="{Binding DataItems}">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Content}"
IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
</local:MyListView>
In the above example the data item class also has Content property. Obviously, the IsSelected property of the data item class must fire a PropertyChanged event.

ICommand Button binding in Itemcontrol

Have xaml.cs file containing my ObservableCollection of my ViewModel. I have now implemented a command binding to a button click which invokes my function inside the viewmodel. The problem is that I do not get the item of my list in my button click function
xaml
<ItemsControl ItemsSource="{Binding ConditionList}" AlternationCount="{Binding ConditionList.Count}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Button Content="{Binding}" Command="{Binding DataContext.DeleteCondition,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding}" />
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Please note my button is in ItemControl
VM
private void DoDeleteCondition(object parameter)
{
// if (parameter != null)
// ...
}
public ICommand DeleteCondition
{
get
{
if (_DeleteCondition == null)
_DeleteCondition = new RelayCommand(o => DoDeleteCondition(o));
return _DeleteCondition;
}
}
You need to create a RelayCommand<T> where T is the Item in the ConditionList. Then you will get your parameter in the execute method.
I have a feeling that your binding is set a little backwards.
In your ItemsControl do you want to have:
the items from your collection and one command that will execute when you click on the single item
or the list of possible commands you want to execute on a single item that you have elsewhere (meaning the collection is displayed on some parent element, so you can bind to the single item somehow)?
... or maybe you have a separate command defined for every item in your collection ...? (then, how are the elements in your collection implemented?)
Depending on your answer:
1:
<ItemsControl ItemsSource="{Binding Path=MyObservableCollection}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Button Content="{Binding}"
Command="{Binding Path=DataContext.DeleteCondition, RelativeSource={RelativeSource AncestorType=AncestorWithYourViewModelAsDataContext}}"
CommandParameter="{Binding}" />
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
2:
<ItemsControl ItemsSource="{Binding Path=ConditionList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Button Content="{Binding}"
Command="{Binding Path=MyConditionalCommand}"
CommandParameter="{BindingToTheElementOfYourCllectionThatYouWantToActUpon}" />
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
example implementation in your ViewModel:
private List<ConditionalCommand> _ConditionList;
public List<ConditionalCommand> ConditionList
{
get { return _ConditionList; }
set
{
if (_ConditionList != value)
{
_ConditionList = value;
OnPropertyChanged("ConditionList");
}
}
}
...
class ConditionalCommand
{
public ICommand MyConditionalCommand { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
...
this.ConditionList = new List<ConditionalCommand>();
this.ConditionList.Add(new ConditionalCommand{ MyConditionalCommand = DeleteCondition , Name="Delete"});
this.ConditionList.Add(new ConditionalCommand{ MyConditionalCommand = DeleteSpecial, Name="Delete special" });
....
private void DoDeleteCondition(object parameter)
{
// if (parameter != null)
// ...
}
public ICommand DeleteCondition
{
get
{
if (_DeleteCondition == null)
_DeleteCondition = new RelayCommand(o => DoDeleteCondition(o));
return _DeleteCondition;
}
}
// DeleteSpecial implemented in similar manner...

How to get a property of a type when Binding

I have a type called "MyType" and my Pivot's ItemsSource is bound to an ObservableCollection property called "DataSource" inside the "myFirstVM" ViewModel. Inside "MyType" i have the property Title. As you can see from my XAML the TextBlock is bound to MyProperty. How to make it return the current item Title?
So for example, If i am on the second PivotItem, I need the Title of the second item in the DataSource collection
XAML:
<Grid x:Name="LayoutRoot">
<Pivot Name="myPivot"
SelectedItem="{Binding myFirstVM.SelItem, Mode=TwoWay}"
ItemsSource="{Binding myFirstVM.DataSource}"
ItemTemplate="{Binding myFirstVM.OtherTemplate}">
<Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.myFirstVM.MyProperty, ElementName=myPivot}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
</Pivot>
</Grid>
myFirstVM code:
private ObservableCollection<MyType> _dataSource;
public ObservableCollection<MyType> DataSource
{
get
{
if (this._dataSource == null)
{
this._dataSource = new ObservableCollection<MyType>();
}
return this._dataSource;
}
set { }
}
public string MyProperty
{
get
{
if (null != this.SelItem)
{
return this.SelItem.Title;
}
return "no title";
}
set { }
}
private MyType _selItem;
public MyType SelItem
{
get
{
return _selItem;
}
set
{
_selItem = value;
RaisePropertyChanged("SelItem");
RaisePropertyChanged("MyProperty");
}
}
public ObservableCollection<MyOtherType> OtherDataSource
{
get
{
if (null != this.SelItem)
{
return this.SelItem.OtherCollection;
}
else
{
return new ObservableCollection<MyOtherType>();
}
}
set { }
}
private MyOtherType _selOtherItem;
public MyOtherType SelOtherItem
{
get
{
return _selSegment;
}
set
{
_selSegment = value;
RaisePropertyChanged("SelOtherItem");
RaisePropertyChanged("PartsDataSource");
}
}
public ObservableCollection<MyThirdType> ThirdDataSource
{
get
{
if (null != this.SelOtherItem)
{
return this.SelOtherItem.ThirdCollection;
}
else
{
return new ObservableCollection<MyThirdType>();
}
}
set { }
}
And these are my DataTemplates for the inner collections "OtherDataSource" and "ThirdDataSource", that are ListBoxes:
<DataTemplate x:Key="OtherTemplate">
<ListBox DataContext="{Binding Source={StaticResource Locator}}"
ItemsSource="{Binding myFirstVM.OtherDataSource}"
ItemTemplate="{StaticResource ThirdTemplate}"
SelectedItem="{Binding myFirstVM.SelOtherItem, Mode=TwoWay}">
</ListBox>
</DataTemplate>
<DataTemplate x:Key="ThirdTemplate">
<ListBox DataContext="{Binding Source={StaticResource Locator}}"
ItemsSource="{Binding myFirstVM.ThirdDataSource}"
ItemTemplate="{StaticResource FourthTemplate}">
</ListBox>
</DataTemplate>
EDIT: I updated the question with the full ViewModel, and the DataTemplates, as sugested by #olitee. The problem with this approach as you can see is that in the second, and third dataTemplate I have ListBoxes. I am using one ViewModel for all the things. Any ideas?
You need to do a little extra work. Your ViewModel is not currently aware of which item is selected. You could create a new property called 'SelectedItem', and bind the Pivots' SelectedItem value.
Then you can access the Selected Item in code.
<Grid x:Name="LayoutRoot">
<Pivot Name="myPivot"
Tag="{Binding}"
SelectedItem="{Binding myFirstVM.SelectedItem}"
ItemsSource="{Binding myFirstVM.DataSource}"
ItemTemplate="{Binding myFirstVM.ViewDataTemplate}">
<Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.myFirstVM.MyProperty, ElementName=myPivot}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
</Pivot>
</Grid>
Then your VM would look something like:
private ObservableCollection<MyType> _dataSource;
public ObservableCollection<MyType> DataSource
{
get
{
if (this._dataSource == null)
{
this._dataSource = new ObservableCollection<MyType>();
}
return this._dataSource;
}
set { }
}
public string MyProperty
{
get
{
if (this.SelectedItem != null)
{
return this.SelectedItem.Title;
}
else
{
return null;
}
}
}
private MyType _selectedItem;
public MyType SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
OnNotifyPropertyChanged("SelectedItem");
OnNotifyPropertyChanged("MyProperty");
}
}
Alternatively, if you're just wanting to fix up the text for presentation, and don't really require the SelectedItem in your VM, you could go with #Jehof's approach - but implement an IValueConvertor that performs the fix.
This should do the trick
<TextBlock Text="{Binding SelectedItem.Title, ElementName=myPivot}"/>
Bind the Text-Property to the SelectedItem property of the Pivot element. When the selected item of the Pivot changes the TextBlock should display the Title of the item.
This is the right XAML implementation:
SelectedItem="{Binding myFirstVM.SelectedItem, Mode=TwoWay}"
and in the code behind instead of OnNotifyPropertyChanged the ViewModel needs to inherit ViewModelBase, part of MVVM Light then in the setter of SelectedItem property:
RaisePropertyChanged("SelectedItem");

Multi-Select ListBox Binding Issue

I have created a custom control derived from a ListBox and I am having issues getting the "SelectedItemsList" to bind to it's corresponding property in the view model.
The problem: The selected items in the list box do not make it into the property it is bound to in the view model. The list box allows multiple selections but none of these make into the List in the view model.
MultiSelectListBox:
public class MultiSelectListBox : ListBox
{
public MultiSelectListBox() { }
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register(
"SelectedItemsList",
typeof(IList),
typeof(MultiSelectListBox),
new PropertyMetadata(default(IList)));
public IList SelectedItemsList
{
get { return (IList) GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
}
declaration in MainWindow.xaml:
<local:MultiSelectListBox
DataContext="{StaticResource viewModel}"
DockPanel.Dock="Left"
Visibility="{Binding IsThailandFinal, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}"
ItemsSource="{Binding SelectedOutputtapeList}"
SelectionMode="Multiple"
SelectedItemsList="{Binding SelectedOutputTapes, Mode=TwoWay}"
HorizontalAlignment="Right"
Background="DeepSkyBlue"
Foreground="MidnightBlue"
ScrollViewer.VerticalScrollBarVisibility="Visible"
Height="100"
Width="70"
Margin="5"/>
View Model (simplified):
public class BTLogFrontEndViewModel : ViewModelBase
{
private List<string> selectedOutputTapes;
public BTLogFrontEndViewModel()
{
selectedOutputTapes = new List<string>();
}
public List<string> SelectedOutputTapes
{
get
{
return selectedOutputTapes;
}
set
{
selectedOutputTapes = value;
OnPropertyChanged("SelectedOutputTapes");
}
}
}
One way is to not use a custom ListBox and use objects in your list that extend INotifyPropertyChanged:
<ListBox
Width="70"
Height="100"
HorizontalAlignment="Right"
Margin="5"
Background="DeepSkyBlue"
DockPanel.Dock="Left"
Foreground="MidnightBlue"
ItemsSource="{Binding SelectedOutputtapeList}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectionMode="Multiple"
DisplayMemberPath="Description"
Visibility="{Binding IsThailandFinal, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Assuming BTLogFrontEndViewModel is your DataContext:
public class BTLogFrontEndViewModel : ViewModelBase
{
private ObservableCollection<OutputTapeViewModel> m_SelectedOutputtapeList;
public ObservableCollection<OutputTapeViewModel> SelectedOutputtapeList
{
get
{
return m_SelectedOutputtapeList;
}
set
{
m_SelectedOutputtapeList = value;
NotifyPropertyChanged("SelectedOutputtapeList");
}
}
}
Where OutputTapeViewModel is declared like:
public class OutputTapeViewModel : ViewModelBase
{
private string m_Description;
public string Description
{
get
{
return m_Description;
}
set
{
m_Description = value;
NotifyPropertyChanged("Description");
}
}
private bool m_IsSelected;
public string IsSelected
{
get
{
return m_IsSelected;
}
set
{
m_IsSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
}
Important things to note is on the listbox I have added the DisplayMemberPath property for use of the description field in the OutputTapeViewModel class for what to show on the listbox. Also it has an item container style, which binds to the OutputTapeViewModel.IsSelected property when selected in the ListBox. This OutputTapeViewModel.IsSelected allows you to use the BTLogFrontEndViewModel.SelectedOutputtapeList property in such ways as:
var selectedItems = SelectedOutputtapeList.Where(item => item.IsSelected);
This only works in your case if you don't care about the overhead of doing the LINQ expression.

Categories