Access property of object in View from ViewModel - wpf

I have a TabControl in my View with multiple TabItems. I would like to change the its IsSelected property of one of the TabItems from my ViewModel.
Here is the xaml code for the View:
<TabControl Height="50" Margin="12,0,0,0">
<TabItem Name="tiCaptureSetup" >
<TabItem.Header>
<Button Name="btnCaptureSetup"
Grid.Column="0"
Width="90"
Height="40"
Margin="5"
ToolTip="Capture Setup"
Content="Capture Setup"
Click="btnCaptureSetup_Click"
IsEnabled="{Binding Path=CaptureSetupButtonStatus, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
IsDefault="True"
></Button>
</TabItem.Header>
</TabItem>
Here is the C# code behind in View
private void btnCaptureSetup_Click(object sender, RoutedEventArgs e)
{
tiCaptureSetup.IsSelected = true; //select Capture Setup TabItem
MenuLSViewModel vm = (MenuLSViewModel)this.DataContext;
if (vm != null)
{
vm.CaptureSetupCommand.Execute(null);
}
}
And I would like to change tiCaptureSetup.IsSelected from ViewModel.
Any suggestions?

Simplest way: Make a property in your ViewModel called something like IsCaptureSetupSelected and bind it to the IsSelected property of tiCaptureSetup.
ViewModel:
private bool _IsCaptureSetupSelected;
public bool IsCaptureSetupSelected
{
get { return _IsCaptureSetupSelected; }
set
{
if (_IsCaptureSetupSelected != value)
{
_IsCaptureSetupSelected = value;
RaisePropertyChanged();
}
}
}
XAML:
<TabItem Name="tiCaptureSetup" IsSelected="{Binding IsCaptureSetupSelected}">
Note that I'm assuming you're using something like MVVMLight with your ViewModel...

Related

WPF - Delete selected item (BitmapImage) from listview

I have a Listview and for it's ItemsSource I have set CollectionOfCapturedImages (an ObservableCollection) ,an a Button for deleting selected items (BitmapImage) from Listview and also from ObservableCollection and a Label in my MainWindow displaying amount of captured images.
private void addNewImageButton_Click(object sender, RoutedEventArgs e)
{
CameraWindow cWindow = Application.Current.Windows.OfType<CameraWindow>().FirstOrDefault();
RoutedEventArgs newEventArgs = new RoutedEventArgs(Button.ClickEvent);
cWindow.manualCapture.RaiseEvent(newEventArgs);
// ListView.ScrollIntoView(ListView.Items.Count - 1);
}
public ObservableCollection<BitmapImage> CollectionOfCapturedImages { get; } = new ObservableCollection<BitmapImage>();
<ListView x:Name="ListView" ItemsSource="{Binding CollectionOfCapturedImages}" Height="345" Margin="567,10,10,0" VerticalAlignment="Top">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" HorizontalAlignment="Center" VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Image Source="{Binding}" Height="150" Width="150"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
this is how I'm updating my Label in MainWindow. When I'm capturing images there is no problem, Label's content is getting updated.
public Action<int> amountOfCapturedImages;
this.cameraWindow = new CameraWindow(this);
cameraWindow.amountOfCapturedImages += (int count) => {
MwAmountOfImagesLabel.Content = count;
};
and here is my delete button
<Button x:Name="DeleteSelectedImageButton" Click="DeleteSelectedImageButton_Click" Content="Delete Selected Image" HorizontalAlignment="Left" Margin="567,488,0,0" Height="26" VerticalAlignment="Top" Width="145"/>
and this way I'm deleting BitmapImages from Listview
private void DeleteSelectedImageButton_Click(object sender, RoutedEventArgs e)
{
CollectionOfCapturedImages.Remove((BitmapImage)ListView.SelectedItem);
}
but my Label's content is not getting updated when I delete an image from Listview. How could I correctly update my Label's content?
You must remove them from the CollectionOfCapturedImages and bind MwAmountOfImagesLabel => CollectionOfCapturedImages.Count
Get rid of this code:
cameraWindow.amountOfCapturedImages += (int count) => {
MwAmountOfImagesLabel.Content = count;
};
...and simply bind the Content property of the Label the Count property of the ObservableCollection:
<Label Content="{Binding CollectionOfCapturedImages.Count}" />
You should not set the Content property of the Label programmatically somewhere in your code. Just set up the binding in your XAML markup.

WPF Binding a Main Window Control from a UserControl View Model

So as mentioned in an earlier question I asked WPF Data Binding From UserControl I have succesfulyl binded the TabHeader of my Control based off a value inside my UserControls code behind file using DependencyProperty, and acheived a a similar implementation with INotifyPropertyChanged.
However I now need it to work off the value from the UserControls ViewModel. I can succesfully update the UserControl UI using INotifyPropertyChanged but I am unable to bind this value to the TabItem control in the Main Window as it seems to regonise it.
Is this even possibly or am I barking up the wrong tree?
Main Window (TabControl) <---> UserControl <---> ViewModel
MainWindow.xaml
<Grid>
<TabControl Height="250" HorizontalAlignment="Left" Margin="12,26,0,0" Name="tabControl1" VerticalAlignment="Top" Width="479">
<TabControl.Resources>
<Style TargetType="TabItem" x:Key="tab1ItemHeaderStyle" >
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate DataType="{x:Type TabItem}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabItem}}"/>
<Label Content="{Binding Path=SomeFigureVM, ElementName=uc1}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Style="{StaticResource tab1ItemHeaderStyle}" Header="[Tab 1]" Name="tabItem1">
<vw:UserControl1 x:Name="uc1"></vw:UserControl1>
</TabItem>
</TabControl>
</Grid>
UserControl1.xaml
<Grid>
<Label Height="43" HorizontalAlignment="Left" Margin="69,128,0,0" Name="textBlock" Content="{Binding SomeFigureVM}" VerticalAlignment="Top" Width="100" />
<Button Name="updateSomeFigure" Content="Update.." Click="updateSomeFigure_Click" Width="100" Height="100" Margin="69,12,66,71" />
</Grid>
UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
private void updateSomeFigure_Click(object sender, RoutedEventArgs e)
{
MyViewModel viewmodel = this.DataContext as MyViewModel;
viewmodel.UpdateFigure();
}
}
MyViewModel.cs
public class MyViewModel: INotifyPropertyChanged
{
public MyViewModel()
{
this.SomeFigureVM = 23;
}
private int _someFigure;
public int SomeFigureVM
{
get
{
return _someFigure ;
}
set
{
_someFigure = value;
NotifyPropertyChanged("SomeFigureVM");
}
}
public void UpdateFigure()
{
SomeFigureVM = SomeFigureVM + 1;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
As always, any help is greatly appreciated, I feel like I've been smashing my head against a brick wall on this one!
SomeFigureVM is a property on your MyViewModel, which is the DataContext for UserControl1. You are trying to access SomeFigureVM prperty on UserControl, which doesn't exist.
Change this line:
<Label Content="{Binding Path=SomeFigureVM, ElementName=uc1}"/>
to
<Label Content="{Binding Path=DataContext.SomeFigureVM, ElementName=uc1}"/>
To catch data binding errors like this, run the application in debug mode and watch the output window for any data binding issues. Your original code generates a data binding error like:
System.Windows.Data Error: 40 : BindingExpression path error:
'SomeFigureVM' property not found on 'object' ''UserControl1'
(Name='uc1')'. BindingExpression:Path=SomeFigureVM;
DataItem='UserControl1' (Name='uc1'); target element is 'Label'
(Name=''); target property is 'Content' (type 'Object')

ContextMenu StaysOpen is not working

I am showing a UserControl inside ContextMenu. Futhermore in that UserControl I am showing a Popup which contains some buttons and datagrid. All the data in Popup loads during runtime.
The problem is ContextMenu ignores the property StaysOpen even though it is set to true.
The Popup does stay open when I set StaysOpen to true in code behind but ContextMenu doesn't.
I tried it with following code:
<UserControl x:Class="UserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Popup Name="popupState" PlacementTarget="{Binding ElementName=txtSearch}" PopupAnimation="Slide" Placement="Bottom" Focusable="True" AllowsTransparency="True" VerticalAlignment="Top">
<Button HorizontalAlignment="Right" Margin="5" Background="GhostWhite" Name="btnSelectAll" Click="btnSelectAll_Click" Width="30" Height="30">
<my:DataGrid VerticalAlignment="Stretch" MaxHeight="300" VerticalScrollBarVisibility="Auto" RowHeaderWidth="0" Margin="5,5,5,1" Background="White" HorizontalAlignment="Stretch" Name="DGTeamCommunicator" HorizontalContentAlignment="Left" HorizontalGridLinesBrush="#D6D7D6" GridLinesVisibility="None">
<my:DataGridTemplateColumn Width="Auto" MinWidth="30">
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" ContextMenuService.IsEnabled="True" ContextMenuService.HasDropShadow="True">
<Button Name="btnCall" Click="btnCall_Click" ContextMenuService.IsEnabled="True">
</Button>
</StackPanel>
</DataTemplate>
</my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>
</my:DataGrid>
</Popup>
My requirement is to Prevent the Popup as well as ContextMenu from closing when the buttons in Popup is clicked.
Kindly provide me a solution for this.
The bad news is that this behavior is by design. ContextMenu is defined to display few menus and when one of them is being clicked the ContextMenu internally sets the IsOpen to false.
The good news is that behavior should stay so and for any other customization you should take a Popup instead of ContextMenu. BUT if you MUST use ContextMenu for whatever reason here is a workaround:
<StackPanel>
<Button>
Popup Demo
<Button.ContextMenu>
<local:StaysOpenContextMenu x:Name="ContextMenu1" StaysOpen="True">
<StackPanel>
<TextBox x:Name="TextBox1" Width="100" TextChanged="OnTextChanged"/>
<Popup x:Name="Popup1" Placement="Bottom" PlacementTarget="{Binding ElementName=tbx}" StaysOpen="True">
<Button Content="click me"/>
</Popup>
</StackPanel>
</local:StaysOpenContextMenu>
</Button.ContextMenu>
</Button>
<Button Click="OnClick">Close Popup</Button>
</StackPanel>
You will need a custom ContextMenu to get the job done:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
this.Popup1.IsOpen = true;
}
private void OnClick(object sender, RoutedEventArgs e)
{
this.Popup1.IsOpen = false;
this.ContextMenu1.CloseContextMenu();
}
}
public class StaysOpenContextMenu : ContextMenu
{
private bool mustStayOpen;
static StaysOpenContextMenu()
{
IsOpenProperty.OverrideMetadata(
typeof(StaysOpenContextMenu),
new FrameworkPropertyMetadata(false, null, CoerceIsOpen));
StaysOpenProperty.OverrideMetadata(
typeof(StaysOpenContextMenu),
new FrameworkPropertyMetadata(false, PropertyChanged, CoerceStaysOpen));
}
private static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
StaysOpenContextMenu menu = (StaysOpenContextMenu)d;
menu.mustStayOpen = (bool)e.NewValue;
}
private static object CoerceStaysOpen(DependencyObject d, object basevalue)
{
d.CoerceValue(IsOpenProperty);
return basevalue;
}
private static object CoerceIsOpen(DependencyObject d, object basevalue)
{
StaysOpenContextMenu menu = (StaysOpenContextMenu)d;
if (menu.StaysOpen && menu.mustStayOpen)
{
return true;
}
return basevalue;
}
public void CloseContextMenu()
{
this.mustStayOpen = false;
this.IsOpen = false;
}
}

how to know which treeview item is clicked using mvvm

I am having a WPF MVVM application which has a TreeView with all the static items maintained in XAML page. How do I know in my view-model which MenuItem is clicked so that I can show that respective page accordingly.
<TreeView Height="Auto" HorizontalAlignment="Stretch" Margin="0" Name="MyTreeViewMenu"
VerticalAlignment="Stretch" Width="Auto" Opacity="1"
BorderThickness="1" BorderBrush="Black" Grid.Row="2">
<TreeViewItem Header="Country" Width="Auto" HorizontalAlignment="Stretch"
></TreeViewItem>
<TreeViewItem Header="View Details" Width="Auto" HorizontalAlignment="Stretch" IsEnabled="False">
<TreeViewItem Header="User" />
<TreeViewItem Header="Group" />
<TreeViewItem Header="User Group" />
</TreeViewItem>
</TreeView>
I suppose that Selected event will have same effect as a click in your case. To determine which one TreeViewItem was selected you should add event Trigger:
<TreeView Height="Auto" HorizontalAlignment="Stretch" Margin="0" Name="MyTreeViewMenu"
VerticalAlignment="Stretch" Width="Auto" Opacity="1"
BorderThickness="1" BorderBrush="Black" Grid.Row="2">
<TreeViewItem Header="Country" Width="Auto" HorizontalAlignment="Stretch"></TreeViewItem>
<TreeViewItem Header="View Details" Width="Auto" HorizontalAlignment="Stretch" IsEnabled="False">
<TreeViewItem Header="User" />
<TreeViewItem Header="Group" />
<TreeViewItem Header="User Group" />
</TreeViewItem>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction
Command="{Binding selectItemCommand}"
CommandParameter="{Binding SelectedItem, ElementName=MyTreeViewMenu}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
As a result you can use and determine which item was selected by a parameter passed to Command.
ViewModel should look something like this:
private ICommand _selectItemCommand;
public ICommand selectItemCommand
{
get
{
return _selectItemCommand ?? (_selectItemCommand = new RelayCommand(param => this.LoadPage(param)));
}
}
private void LoadPage(object selectedMenuItem)
{
...
}
Take a look at the TreeView.SelectedItem Property page at MSDN.
You can bind directly to the TreeView.SelectedItem property:
<TreeView ItemsSource="{Binding Items}" SelectedItem="{Binding Item, Mode=OneWay}" />
Note that the TreeView.SelectedItem property is only read only, so you must use a OneWay binding... this means that you cannot set the selected item from your view model. To do that, you will need to create your own two way selected item property using an Attached Property.
EDIT >>>
My apologies #Scroog1, I normally use an AttachedProperty to do this. You are right that even with a OneWay binding, there is an error using this method. Unfortuately, my AttachedProperty code is long, but there is another way to do this.
I wouldn't necessarily recommend this as it's never really a good idea to put UI properties into your data objects, but if you add an IsSelected property to your data object, then you can bind it directly to the TreeViewItem.IsSelected property:
<TreeView ItemsSource="Items" HorizontalAlignment="Stretch" ... Name="MyTreeViewMenu">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
I just searched and found a 'fuller' answer for you in the WPF MVVM TreeView SelectedItem post here on StackOverflow.
Alternatively, there is another way... you could also use the TreeView.SelectedValue and TreeView.SelectedValuePath properties. The basic idea is to set the TreeView.SelectedValuePath property to the name of a property on your data object. When an item is selected, the TreeView.SelectedValue property will then be set to the value of that property of the selected data item. You can find out more about this method from the How to: Use SelectedValue, SelectedValuePath, and SelectedItem page at MSDN. This generally works best if you have a uniquely identifiable property like an identifier of some kind. This code example is from MSDN:
<TreeView ItemsSource="{Binding Source={StaticResource myEmployeeData},
XPath=EmployeeInfo}" Name="myTreeView" SelectedValuePath="EmployeeNumber" />
<TextBlock Margin="10">SelectedValuePath: </TextBlock>
<TextBlock Margin="10,0,0,0" Text="{Binding ElementName=myTreeView,
Path=SelectedValuePath}" Foreground="Blue"/>
<TextBlock Margin="10">SelectedValue: </TextBlock>
<TextBlock Margin="10,0,0,0" Text="{Binding ElementName=myTreeView,
Path=SelectedValue}" Foreground="Blue"/>
In addition to binding to the TreeView.SelectedItem property:
When using MVVM it helped me to stop thinking about events in the UI and start thinking about state in the UI.
You can bind the ViewModel to properties of the View. So in general I try to bind a SelectedItem to a property on the ViewModel so the ViewModel knows what is selected.
In the same way you could add a property to the ViewModel items being shown called Selected and bind this property to a checkbox in the View. That way you can enable multiple selection and access the selected items easily within the ViewModel.
For completeness, here are the attached property and TreeView subclass options:
Attached property option
public static class TreeViewSelectedItemHelper
{
public static readonly DependencyProperty BindableSelectedItemProperty
= DependencyProperty.RegisterAttached(
"BindableSelectedItem",
typeof (object),
typeof (TreeViewSelectedItemHelper),
new FrameworkPropertyMetadata(false,
OnSelectedItemPropertyChanged)
{
BindsTwoWayByDefault = true
});
public static object GetBindableSelectedItem(TreeView treeView)
{
return treeView.GetValue(BindableSelectedItemProperty);
}
public static void SetBindableSelectedItem(
TreeView treeView,
object selectedItem)
{
treeView.SetValue(BindableSelectedItemProperty, selectedItem);
}
private static void OnSelectedItemPropertyChanged(
DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
var treeView = sender as TreeView;
if (treeView == null) return;
SetBindableSelectedItem(treeView, args.NewValue);
treeView.SelectedItemChanged -= HandleSelectedItemChanged;
treeView.SelectedItemChanged += HandleSelectedItemChanged;
if (args.OldValue != args.NewValue)
SetSelected(treeView, args.NewValue);
}
private static void SetSelected(ItemsControl treeViewItem,
object itemToSelect)
{
foreach (var item in treeViewItem.Items)
{
var generator = treeViewItem.ItemContainerGenerator;
var child = (TreeViewItem) generator.ContainerFromItem(item);
if (child == null) continue;
child.IsSelected = (item == itemToSelect);
if (child.HasItems) SetSelected(child, itemToSelect);
}
}
private static void HandleSelectedItemChanged(
object sender,
RoutedPropertyChangedEventArgs<object> args)
{
if (args.NewValue is TreeViewItem) return;
var treeView = sender as TreeView;
if (treeView == null) return;
var binding = BindingOperations.GetBindingExpression(treeView,
BindableSelectedItemProperty);
if (binding == null) return;
var propertyName = binding.ParentBinding.Path.Path;
var property = binding.DataItem.GetType().GetProperty(propertyName);
if (property != null)
property.SetValue(binding.DataItem, treeView.SelectedItem, null);
}
}
Subclass option
public class BindableTreeView : TreeView
{
public BindableTreeView()
{
SelectedItemChanged += HandleSelectedItemChanged;
}
public static readonly DependencyProperty BindableSelectedItemProperty =
DependencyProperty.Register(
"BindableSelectedItem",
typeof (object),
typeof (BindableTreeView),
new FrameworkPropertyMetadata(
default(object),
OnBindableSelectedItemChanged) {BindsTwoWayByDefault = true});
public object BindableSelectedItem
{
get { return GetValue(BindableSelectedItemProperty); }
set { SetValue(BindableSelectedItemProperty, value); }
}
private static void OnBindableSelectedItemChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var treeView = d as TreeView;
if (treeView != null) SetSelected(treeView, e.NewValue);
}
private static void SetSelected(ItemsControl treeViewItem,
object itemToSelect)
{
foreach (var item in treeViewItem.Items)
{
var generator = treeViewItem.ItemContainerGenerator;
var child = (TreeViewItem) generator.ContainerFromItem(item);
if (child == null) continue;
child.IsSelected = (item == itemToSelect);
if (child.HasItems) SetSelected(child, itemToSelect);
}
}
private void HandleSelectedItemChanged(
object sender,
RoutedPropertyChangedEventArgs<object> e)
{
SetValue(BindableSelectedItemProperty, SelectedItem);
}
}

Binding a listbox SelectedItem to an Observable Collection?

I have a Listbox in WPF with the SelectionMode set to Multiple, and can multiselect the items in the Listbox. However, the SelectedItem is not updating the Observable Collection it is bound to.
Is there a way to bind the multiple selected items from a ListBox to an Observable Collection?
i dont know mvvm way of doing this,
i have a working solution comibined of mvvm & codebehind.
CodeBehind
private void lstbox_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
var listBox = sender as ListBox;
if (listBox == null) return;
var viewModel = listBox.DataContext as Window1ViewModel;
if (viewModel == null) return;
viewModel.ListOfSelectedItems.Clear();
foreach (Window1ViewModel.States item in listBox.SelectedItems)
{
viewModel.ListOfSelectedItems.Add(item);
}
}
ViewModel
private ObservableCollection<States> _listofselecteditems;
public ObservableCollection<States> ListOfSelectedItems
{
get
{
return _listofselecteditems;
}
set
{
_listofselecteditems = value;
RaisePropertyChanged(() => ListOfSelectedItems);
}
}
Xaml
<ListBox x:Name="lstbox"
SelectionChanged="lstbox_SelectionChanged_1"
ItemsSource="{Binding StatesList,Mode=TwoWay}"
SelectionMode="Multiple" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox
IsChecked="{Binding Path=IsSelected,Mode=TwoWay}"
Content="{Binding StateName}" />
<TextBox Margin="8,0,0,0" Text="{Binding SOmeProperty}" IsEnabled="{Binding Path=IsSelected}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>

Resources