I bound the "WindowState" property of my main window to my ViewModel in order to change the state of the window by a command, but the first time I minimize the window it minimizes like a worksheet does in an Excel file. Is there a work around for this or a correct way to bind the "WindowState" property to my ViewModel so that the window minimizes correctly?
this is a sample work around that tested with Relaying Command Logic. You will get more detail on WPF Apps With The Model-View-ViewModel Design Pattern .
<Window x:Class="WpfMvvmTestCSharp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfMvvmTestCSharp"
Title="Window1" Height="300" Width="300" WindowState="{Binding CurWindowState, Mode=TwoWay}">
<Window.DataContext>
<vm:Window1ViewModel/>
</Window.DataContext>
<Grid>
<Button Command="{Binding CmdMax}" Height="23" Margin="12,25,0,0" Name="button1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="75">Maximize</Button>
<Button Command="{Binding CmdMin}" Height="23" Margin="101,25,102,0" Name="button2" VerticalAlignment="Top">Minimize</Button>
<Button Command="{Binding CmdRes}" Height="23" HorizontalAlignment="Right" Margin="0,25,13,0" Name="button3" VerticalAlignment="Top" Width="75">Restore</Button>
</Grid>
</Window>
and in the Windows ViewModel
class Window1ViewModel:ViewModelBase
{
public Window1ViewModel()
{
CurWindowState = WindowState.Maximized;
}
public ICommand CmdMax
{
get { return new RelayCommand(param => onCmdMax()); }
}
void onCmdMax()
{
CurWindowState = WindowState.Maximized;
}
public ICommand CmdMin
{
get { return new RelayCommand(param => onCmdMin()); }
}
void onCmdMin()
{
CurWindowState = WindowState.Minimized;
}
public ICommand CmdRes
{
get { return new RelayCommand(param => onCmdRes()); }
}
void onCmdRes()
{
CurWindowState = WindowState.Normal;
}
private WindowState _curWindowState;
public WindowState CurWindowState
{
get
{
return _curWindowState;
}
set
{
_curWindowState = value;
base.OnPropertyChanged("CurWindowState");
}
}
}
I don't think you should care about the window state in a view model, it's completely wrong because a lower-level layer is aware of a higher-level layer (thus wrong Separation of Concerns (SOC)).
What I normally do in this case is subscribe to changes in the view model from the code-behind of the control or window (thus the view) containing the view model. In this case, it is valid to write code in the code-behind because it is only used in the view (and thus the code-behind is the perfect location for this logic, which you really don't want to unit test).
Another option to consider is subscribing both via a command AND an event to code behind, e.g:
<Button Command="{Binding SnoozeCommand}" Click="Button_Click">Snooze</Button>
The command in this case affects the VM. The Click event, only changes the Window state.
I have found my own solution which is perfectly suited to MVVM. I'm using behavior to find the parent window of the user control and track WindowState changes.
public class WindowStateBehavior : Behavior<UserControl>
{
public static readonly DependencyProperty WindowStateProperty =
DependencyProperty.Register(nameof(WindowState), typeof(WindowState), typeof(WindowStateBehavior),
new FrameworkPropertyMetadata(default(WindowState), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
private Window window;
public WindowState WindowState
{
get => (WindowState) GetValue(WindowStateProperty);
set => SetCurrentValue(WindowStateProperty, value);
}
protected override void OnAttached()
{
base.OnAttached();
this.window = Window.GetWindow(this.AssociatedObject)!;
this.window.StateChanged += this.OnStateChanged;
}
private void OnStateChanged(object sender, EventArgs e) => this.WindowState = this.window.WindowState;
}
This behavior can be used in any UserControl like this with bound WindowState in ViewModel.
<UserControl x:Class="RCBase.WPF.Monitor.CustomUserView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviors="clr-namespace:RCBase.WPF.Behaviors"
mc:Ignorable="d" d:DataContext="{d:DesignInstance monitor:CustomUserViewModel}">
<i:Interaction.Behaviors>
<behaviors:WindowStateBehavior WindowState="{Binding WindowState}" />
</i:Interaction.Behaviors>
Related
I have been doing development work in WPF application which uses an MVVM pattern for a couple of days now. I'm very new to WPF and MVVM pattern as well.
In my scenario, I have a user control view (named EPayView.xaml) which has a textbox that will accept a phone number. The view has a corresponding viewmodel (named EPayViewModel.cs). In the MainWindow.xaml, I have a user control (floating virtual keyboard) which is derived from namespace controls WpfKb.Controls. The MainWindow.xaml also has a corresponding viewmodel (named MainViewModel.cs)
Having said that, I have done research on how to use attached dependency properties which lead me to this solution. Set focus on textbox in WPF from view model (C#) which I believe this is where I could bind the property IsFocused in the textbox of EPayView.xaml.
Below are the codes that I have already incorporated in my solution.
EpayView.xaml (textbox xaml markup)
<TextBox Text="{Binding PhoneNo}" Grid.Row="5" Margin="10,0,10,0" VerticalContentAlignment="Center" FontSize="12" x:Name="Email" behaviors:FocusExtension.IsFocused="{Binding IsFocused, Mode=TwoWay}"/>
MainWindow.xaml (xaml markup)
<Window x:Class="SmartPole540.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WpfKb.Controls;assembly=SmartPole.WpfKb"
xmlns:wpf="clr-namespace:WebEye.Controls.Wpf;assembly=WebEye.Controls.Wpf.WebCameraControl"
xmlns:utilities="clr-namespace:SoltaLabs.Avalon.Core.Utilities;assembly=SoltaLabs.Avalon.Core"
xmlns:userControls="clr-namespace:SoltaLabs.Avalon.View.Core.UserControls;assembly=SoltaLabs.Avalon.View.Core"
xmlns:square="clr-namespace:SmartPole.View.Square;assembly=SmartPole.View"
xmlns:view="clr-namespace:SmartPole.View;assembly=SmartPole.View"
Title="CitiPulse"
WindowStartupLocation="Manual"
PreviewMouseLeftButtonDown="Window_PreviewMouseLeftButtonDown"
Name="mainWindow">
<userControls:RollPanel.BottomContent>
<square:SquareView Canvas.Top="1010" DataContext="{Binding DataContext.SquareViewModel,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type userControls:RollPanel}}}"/>
</userControls:RollPanel.BottomContent>
<controls:FloatingTouchScreenKeyboard
x:Name="floatKb" Width="500" Height="250" PlacementTarget="{Binding ElementName=MainGrid}"
Placement="Center" AreAnimationsEnabled="False" Visibility="Visible"
IsOpen="{Binding IsChecked, ElementName=kbButton}"/>
</Window>
In the above code, the user control RollPanel.BottomContent host the EPayView.xaml view inside another view which is RollPanel.xaml
EpayViewModel.cs contains the static class FocusExtension for the IsFocused attached property (refer to this solution - Set focus on textbox in WPF from view model (C#)). And, EPayViewModel.cs already implemented INotifyPropertyChanged which is wrapped inside a concrete class ObservableObject that accepts type of T. This is also same with MainViewModel.cs
public class EPayViewModel : ObservableObject<EPayViewModel>, IPaymentViewModel, IActiveViewModel
{ ... }
public class MainViewModel : ObservableObject<MainViewModel>
{ ... }
As such, my goal is that when the textbox in EPayView.xaml has the focus, the floating virtual keyboard (floatKb) in the MainWindow.xaml will be shown.
I'm stuck on how to proceed (I was thinking if a call to FocusExtension static class in EPayViewModel inside my MainViewModel.cs will suffice?), any help is greatly appreciated.
Cheers,
As AnjumSKhan already said, to react to some event in a MVVM way, you'll have to use Command. Command can be called within an EventTrigger, you will need to add a Reference to System.Windows.Interactvity component.
Let's assume you have a simple View and View Model and you need to show this View when the TextBox in a MainWindow got focus.
View (NewWindow.xaml)
<Window x:Class="My.NewWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NewWindow" Height="300" Width="300">
<TextBlock Text="{Binding Message}"/>
View Model
public class NewWindowViewModel
{
private string _message;
public string Message
{
get { return _message; }
set { _message = value; }
}
}
You also have a MainWindow, it is a main view for an app and it contains the target TextBox. You may see that there is an EventTrigger added to the TextBox and it has a property InvokeCommandAction which is binded to the MainWindowViewModel's command called ShowCommand.
Main Window
<Window x:Class="My.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" Title="MainWindow" Height="350" Width="525">
<TextBox Height="40" Text="{Binding Text}">
<Interactivity:Interaction.Triggers>
<Interactivity:EventTrigger EventName="GotFocus">
<Interactivity:InvokeCommandAction Command="{Binding ShowCommand}"/>
</Interactivity:EventTrigger>
</Interactivity:Interaction.Triggers>
</TextBox>
In the Show method of MainWindowViewModel NewWindow view is created and got new NewWindowViewModel instance as a DataContext. RelayCommand class is presented in my answer to this question
MainWindowViewModel
public class MainWindowViewModel
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; }
}
private ICommand _increaseCommand;
public ICommand ShowCommand
{
get
{
if (_increaseCommand == null)
{
_increaseCommand = new RelayCommand(
p => true,
Show);
}
return _increaseCommand;
}
}
private void Show(object obj)
{
var w = new NewWindow();
var nvm = new NewWindowViewModel();
nvm.Message = "Test";
w.DataContext = nvm;
w.Show();
}
}
What is left is to create a new MainWindowViewModel and setup a DataContext for MainWindow.
public MainWindow()
{
InitializeComponent();
var mvm = new MainWindowViewModel();
mvm.Text = "Focus me!";
DataContext = mvm;
}
Hope it will help.
I'l start by letting a picture do some talking.
So you see, I want to create a WPF user control that supports binding to a parent window's DataContext. The user control is simply a Button and a ListBox with a custom ItemTemplate to present things with a Label and a Remove Button.
The Add button should call an ICommand on the main view model to interact with the user in selecting a new thing (instance of IThing). The Remove buttons in the ListBoxItem in the user control should similarly call an ICommand on the main view model to request the related thing's removal. For that to work, the Remove button would have to send some identifying information to the view model about the thing requesting to be removed. So there are 2 types of Command that should be bindable to this control. Something like AddThingCommand() and RemoveThingCommand(IThing thing).
I got the functionality working using Click events, but that feels hacky, producing a bunch of code behind the XAML, and rubs against the rest of the pristine MVVM implementation. I really want to use Commands and MVVM normally.
There's enough code involved to get a basic demo working, I am holding off on posting the whole thing to reduce confusion. What is working that makes me feel like I'm so close is the DataTemplate for the ListBox binds the Label correctly, and when the parent window adds items to the collection, they show up.
<Label Content="{Binding Path=DisplayName}" />
While that displays the IThing correctly, the Remove button right next to it does nothing when I click it.
<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
This isn't terribly unexpected since the specific item isn't provided, but the Add button doesn't have to specify anything, and it also fails to call the command.
<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
So what I need is the "basic" fix for the Add button, so that it calls the parent window's command to add a thing, and the more complex fix for the Remove button, so that it also calls the parent command but also passes along its bound thing.
Many thanks for any insights,
This is trivial, and made so by treating your UserControl like what it is--a control (that just happens to be made up from other controls). What does that mean? It means you should place DependencyProperties on your UC to which your ViewModel can bind, like any other control. Buttons expose a Command property, TextBoxes expose a Text property, etc. You need to expose, on the surface of your UserControl, everything you need for it to do its job.
Let's take a trivial (thrown together in under two minutes) example. I'll leave out the ICommand implementation.
First, our Window
<Window x:Class="UCsAndICommands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:UCsAndICommands"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<t:ViewModel />
</Window.DataContext>
<t:ItemsEditor Items="{Binding Items}"
AddItem="{Binding AddItem}"
RemoveItem="{Binding RemoveItem}" />
</Window>
Notice we have our Items editor, which exposes properties for everything it needs--the list of items it is editing, a command to add a new item, and a command to remove an item.
Next, the UserControl
<UserControl x:Class="UCsAndICommands.ItemsEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:t="clr-namespace:UCsAndICommands"
x:Name="root">
<UserControl.Resources>
<DataTemplate DataType="{x:Type t:Item}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveItem, ElementName=root}"
CommandParameter="{Binding}">Remove</Button>
<TextBox Text="{Binding Name}" Width="100"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Button Command="{Binding AddItem, ElementName=root}">Add</Button>
<ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
</StackPanel>
</UserControl>
We bind our controls to the DPs defined on the surface of the UC. Please, don't do any nonsense like DataContext=this; as this anti-pattern breaks more complex UC implementations.
Here's the definitions of these properties on the UC
public partial class ItemsEditor : UserControl
{
#region Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"Items",
typeof(IEnumerable<Item>),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public IEnumerable<Item> Items
{
get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
#endregion
#region AddItem
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register(
"AddItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand AddItem
{
get { return (ICommand)GetValue(AddItemProperty); }
set { SetValue(AddItemProperty, value); }
}
#endregion
#region RemoveItem
public static readonly DependencyProperty RemoveItemProperty =
DependencyProperty.Register(
"RemoveItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand RemoveItem
{
get { return (ICommand)GetValue(RemoveItemProperty); }
set { SetValue(RemoveItemProperty, value); }
}
#endregion
public ItemsEditor()
{
InitializeComponent();
}
}
Just DPs on the surface of the UC. No biggie. And our ViewModel is similarly simple
public class ViewModel
{
public ObservableCollection<Item> Items { get; private set; }
public ICommand AddItem { get; private set; }
public ICommand RemoveItem { get; private set; }
public ViewModel()
{
Items = new ObservableCollection<Item>();
AddItem = new DelegatedCommand<object>(
o => true, o => Items.Add(new Item()));
RemoveItem = new DelegatedCommand<Item>(
i => true, i => Items.Remove(i));
}
}
You are editing three different collections, so you may want to expose more ICommands to make it clear which you are adding/removing. Or you could cheap out and use the CommandParameter to figure it out.
Refer the below code.
UserControl.XAML
<Grid>
<ListBox ItemsSource="{Binding Things}" x:Name="lst">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ThingName}" Margin="3"/>
<Button Content="Remove" Margin="3" Command="{Binding ElementName=lst, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Window.Xaml
<Window x:Class="MultiBind_Learning.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiBind_Learning"
Title="Window1" Height="300" Width="300">
<StackPanel Orientation="Horizontal">
<Button Content="Add" Width="50" Height="25" Command="{Binding AddCommnd }"/>
<local:UserControl2/>
</StackPanel>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ThingViewModel();
}
}
ThingViewModel.cs
class ThingViewModel
{
private ObservableCollection<Thing> things = new ObservableCollection<Thing>();
public ObservableCollection<Thing> Things
{
get { return things; }
set { things = value; }
}
public ICommand AddCommnd { get; set; }
public ICommand RemoveCommand { get; set; }
public ThingViewModel()
{
for (int i = 0; i < 10; i++)
{
things.Add(new Thing() { ThingName="Thing" +i});
}
AddCommnd = new BaseCommand(Add);
RemoveCommand = new BaseCommand(Remove);
}
void Add(object obj)
{
things.Add(new Thing() {ThingName="Added New" });
}
void Remove(object obj)
{
things.Remove((Thing)obj);
}
}
Thing.cs
class Thing :INotifyPropertyChanged
{
private string thingName;
public string ThingName
{
get { return thingName; }
set { thingName = value; OnPropertyChanged("ThingName"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
BaseCommand.cs
public class BaseCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
{
_method = method;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
Instead of Base command you can try RelayCommand from MVVMLight or DelegateCommand from PRISM libraries.
By default, your user control will inherit the DataContext of its container. So the ViewModel class that your window uses can be bound to directly by the user control, using the Binding notation in XAML. There's no need to specify DependentProperties or RoutedEvents, just bind to the command properties as normal.
The task: implement the simplest Dependency Property ever, which can be used in xaml like that:
<uc:MyUserControl1 MyTextProperty="{Binding Text}"/>
I think that this answer is quite close. For better readability i copy all my code here (mostly from that answer above).
<UserControl x:Class="Test.UserControls.MyUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<!-- Text is being bound to outward representative property;
Note the DataContext of the UserControl -->
<TextBox Text="{Binding MyTextProperty}"/>
</Grid>
</UserControl>
and
public partial class MyUserControl1 : UserControl
{
// The dependency property which will be accessible on the UserControl
public static readonly DependencyProperty MyTextPropertyProperty =
DependencyProperty.Register("MyTextProperty", typeof(string), typeof(MyUserControl1), new UIPropertyMetadata(String.Empty));
public string MyTextProperty
{
get { return (string)GetValue(MyTextPropertyProperty); }
set { SetValue(MyTextPropertyProperty, value); }
}
public MyUserControl1()
{
InitializeComponent();
}
}
And this is my MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:uc="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Vertical">
<uc:MyUserControl1 MyTextProperty="my text goes here"/>
<Button Click="ButtonBase_OnClick" Content="click"/>
</StackPanel>
</Window>
So far, everything works. However, i find this quite not usefull. What i'd need is
<uc:MyUserControl1 MyTextProperty="{Binding Text}"/>
and being able to change this by setting a DataContext (as you usually do in MVVM)
So i replace the line as above and add my code behind as follows:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
Text = "Initial Text";
DataContext = this;
}
private string _Text;
public string Text
{
get { return _Text; }
set
{
if (value != _Text)
{
_Text = value;
NotifyPropertyChanged("Text");
}
}
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Text = "clicked";
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Neither the "initial Text" nor the "clicked" is displayed... ever. So my question is how to implement a dept. property correctly to be used with
<uc:MyUserControl1 MyTextProperty="{Binding Text}"/>
The Text property is located on the DataContext of the MainWindow not of the UserControl.
So change this line <uc:MyUserControl1 MyTextProperty="{Binding Text}"/> into this:
<uc:MyUserControl1 MyTextProperty="{Binding Text, ElementName=MyMainWindow}"/>
Which will tell the Binding that you're talking about the Text element located in you MainWindow. Of course, since in this example I used ElementName, you're going to want to name your window MyMainWindow...
So add this to your MainWindow:
<Window Name="MyMainWindow" ..... />
If you rather not name your window, you can use the RelativeSource FindAncestor binding like this:
<wpfApplication6:MyUserControl1 MyTextProperty="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"/>
In both ways, you are asking to find the property named 'Text' in the DataContext of the window.
In my view i'm adding dynamically custom TabItems (TextseiteTabItem). With the property DataContext i gave each TabItem a Model to work with (fill in values). Now i added a close-command to the custom TabItems but it wont work. I cant manage to send the close-command to the viewmodel. Above is my attempt..
My custom TabItem:
<sdk:TabItem x:Class="PortfolioCreator.TextseiteTabItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<sdk:TabItem.Header>
<StackPanel Orientation="Horizontal">
<sdk:Label Content="{Binding Seitennummer, StringFormat='Seite {0}', Mode=TwoWay}"/>
<Button Content="X"
Command="{Binding CloseTabCommand, Mode=TwoWay}"
DataContext="{Binding ElementName=TemplateTabControl}"
CommandParameter="{Binding SelectedItem, ElementName=TemplateTabControl}" />
</StackPanel>
</sdk:TabItem.Header>
<sdk:TabItem.Content>
<Grid x:Name="LayoutRoot">
...
</Grid>
</sdk:TabItem.Content>
</sdk:TabItem>
In my View:
...
<sdk:TabControl toolkit:DockPanel.Dock="Bottom" ItemsSource="{Binding Tabs}" x:Name="TemplateTabControl"/>
...
In my ViewModel:
public class PortfolioViewModel : ViewModelBase
{
public ObservableCollection<TabItem> Tabs { get; set; }
public RelayCommand<TabItem> CloseTabCommand
{
get;
private set;
}
public PortfolioViewModel()
{
CloseTabCommand = new RelayCommand<TabItem>(tab =>
{
//never reached
},
tab =>
{
//never reached
});
Tabs = new ObservableCollection<TabItem>();
AddTextseite();
AddTextseite();
}
void AddTextseite()
{
TabItem item = new TextseiteTabItem();
item.DataContext = new TextSeiteModel();
Tabs.Add(item);
}
}
First, your CloseTabCommand does nothing in your current code snippet: //never reached. The execute handler should read something like tab.Visibility = Visibility.Collapsed or myTabControl.Items.Remove(myTabItem).
Second, as #Rafal pointed out, using UI elements in the ViewModel is not the correct way to implement MVVM. If you want closable tab items, the correct way would be to derive a generic CloseableTabItem control or write a ClosableTabItemBehavior on the UI layer with a settable ICommand CloseCommand that can be bound to the corresponding ICommand instance on the ViewModel. Admittedly this approach might be too elaborate for your project though.
You are attempting to use MVVM but the strange thing I see is collection of ui elements (Tabs) in your view model. The correct way would be to create ViewModel that describes Tab item and move the command there. Then it will bind. To remove tab from Tabs you should expose event in your Tab view model and attach to it form PortfolioViewModel.
Of course my change will cause that your TextseiteTabItem will not show in TablControl. But it can be easily fixed with TabControl.ItemTemplate and TabControl.ContentTemplate.
here you find a demo application with closeable tabs for wpf, maybe it works for your silverlight version also.
This is my workaround for this problem. I admit it is not a good solution and breaks the mvvm pattern but as #herzmeister says other approaches are too elaborate for my project right now. (But it won't be the final solution ;-) )
TabItemViewModel:
public delegate void CloseTabItemHandler();
public class TextseiteTabItemViewModel : ViewModelBase
{
public event CloseTabItemHandler CloseTabItem;
public RelayCommand CloseTabCommand {get; set;}
public TextseiteTabItemViewModel()
{
CloseTabCommand = new RelayCommand(() =>
{
if (CloseTabItem == null) return;
CloseTabItem();
});
}
}
TabItemView:
<sdk:TabItem x:Class="PortfolioCreator.TextseiteTabItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<sdk:TabItem.Header>
<StackPanel Orientation="Horizontal">
<Button Content="X" Command="{Binding CloseTabCommand, Mode=TwoWay}" />
</StackPanel>
</sdk:TabItem.Header>
<sdk:TabItem.Content>
<Grid x:Name="LayoutRoot">
...
</Grid>
</sdk:TabItem.Content>
</sdk:TabItem>
Parent ViewModel:
public class PortfolioViewModel : ViewModelBase
{
public ObservableCollection<TabItem> Tabs { get; set; }
public PortfolioViewModel()
{
Tabs = new ObservableCollection<TabItem>();
AddTextseite();
AddTextseite();
}
void AddTextseite()
{
var viewmodel = new TextseiteTabItemViewModel();
TabItem item = new TextseiteTabItemView();
item.DataContext = viewmodel;
viewmodel.CloseTabItem += new CloseTabItemHandler(() =>
{
Tabs.Remove(item);
});
Tabs.Add(item);
}
}
I'm new to WPF and i'm trying to learn the famous MVVM pattern,
I'm facing a small issue (i'm sure) when i try to bind simple command to some ViewModel
this is Simple UserControl i've created:
<UserControl x:Class="MVVM_UsingUserControl_Sample.View.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
>
<StackPanel DataContext="MyUserControlViewModel" Orientation="Vertical" >
<Button Margin="0,100,0,0" Height="50" Width="50" Content="PressMe" Command="{Binding Path=MyCommand}"/>
</StackPanel>
</UserControl>
and this is the User Control ViewModel
class MyUserControlViewModel : ViewModelBase
{
CommandBase m_MyCommand = null;
public MyUserControlViewModel()
{
}
public ICommand MyCommand
{
get
{
if (m_MyCommand == null)
{
m_MyCommand = new CommandBase(new Action<object>(DoSomething),
new Predicate<object>(CanDoSomething));
}
return m_MyCommand;
}
}
public void DoSomething(object i_Params)
{
MessageBox.Show("Inside MyUserControl DoSomething");
}
public bool CanDoSomething(object i_Params)
{
return true;
}
}
this is the Main window xaml (no code behaind)
Now the problem is :
My main window contains the userControl as is (inside stack panel) and nothing else.
i expect the command "MyCommad" will get invoke when i press the button "MyButton"
but it doesn't.
anyone has idea why ???
Big thanks.
In the constructor of your main window, set its DataContext to your ViewModel.
For example,
this.DataContext = new MyViewModel();
In your XAML, remove
DataContext="MyUserControlViewModel"
since the DataContext will inherit from the main window.
Everything should then work as you expect.