I have a main viewmodel with several child viewmodel
For each childVM, I have definied the following in the main view:
<Window.Resources>
<vm:MainVM x:Key="MainVM" />
<DataTemplate DataType="{x:Type vm:ChildVM}">
<view:childview />
</DataTemplate>
</Window.Resources>
Inside childview there's a textbox with:
<TextBox>
<i:Interaction.Behaviors>
<behavior:AppendTextBehavior AppendTextAction="{Binding AppendTextAction, Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
</TextBox>
The behavior class:
public class AppendTextBehavior : Behavior<TextBox>
{
public Action<string> AppendTextAction
{
get { return (Action<string>)GetValue(AppendTextActionProperty); }
set { SetValue(AppendTextActionProperty, value); }
}
public static readonly DependencyProperty AppendTextActionProperty =
DependencyProperty.Register("AppendTextAction", typeof(Action<string>), typeof(AppendTextBehavior));
protected override void OnAttached()
{
base.OnAttached();
SetCurrentValue(AppendTextActionProperty, (Action<string>)AssociatedObject.AppendText);
AssociatedObject.TextChanged += AssociatedObject_TextChanged;
}
void AssociatedObject_TextChanged(object sender, EventArgs e)
{
// does something
}
}
Main view
<TabControl
ItemsSource="{Binding Tabs, Mode=OneWay}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
..
<Button Content="+" Command="{Binding Source={StaticResource MainVM}, Path=AddTabCommand}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl>
Main ViewModel
private ObservableCollection<ChildVM> tabs;
public ObservableCollection<ChildVM> Tabs
{
get
{
return tabs;
}
set
{
tabs = value;
RaisePropertyChanged(() => Tabs);
}
}
void AddTab()
{
ChildVM tab = new ChildVM();
Tabs.Add(tab);
}
public ICommand AddTabCommand { get { return new MvvmFoundation.Wpf.RelayCommand(AddTab); } }
Finally, ChildVM has:
private Action<string> appendTextAction;
public Action<string> AppendTextAction
{
get { return appendTextAction; }
set {
appendTextAction = value;
RaisePropertyChanged(() => AppendTextAction);
}
}
Now, AppendTextAction's VM is always null, so the binding doesn't work.
BUT, if I try to create the childVM within the view like this:
<UserControl.DataContext>
<vm:ChildVM>
</UserControl.DataContext>
The Binding works perfectly!
Obviously the code above is wrong, because I don't need to create the child view model again
What can I do to make the AppendTextAction binding work ?
Related
I am trying to create a multi-select Combobox Custom control, This custom control should expose a dependency property called DropDownDataSource through which the user of the control can decide what day should bound to ComboBox. My code looks like this:
MainPage.Xaml
<Grid>
<local:CustomComboBox x:Name="customcb" DropDownDataSource="{x:Bind DropDownDataSource, Mode=OneWay}" Loaded="CustomControl_Loaded"> </local:CustomComboBox>
</Grid>
MainPage.Xaml.cs
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private ObservableCollection<Item> _dropDownDataSource;
public ObservableCollection<Item> DropDownDataSource
{
get => _dropDownDataSource;
set
{
_dropDownDataSource = value;
OnPropertyChanged();
}
}
public MainPage()
{
this.InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private void CustomControl_Loaded(object sender, RoutedEventArgs e)
{
var Items = new ObservableCollection<Item>(Enumerable.Range(1, 10)
.Select(x => new Item
{
Text = string.Format("Item {0}", x),
IsChecked = x == 40 ? true : false
}));
DropDownDataSource = Items;
}
}
Models
public class Item : BindableBase
{
public string Text { get; set; }
bool _IsChecked = default;
public bool IsChecked { get { return _IsChecked; } set { SetProperty(ref _IsChecked, value); } }
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value,
[System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (!object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
CustomUserControl XAML
<Grid x:Name="GrdMainContainer">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox Width="200" FontSize="24" Text="{Binding Header, Mode=TwoWay}"
IsReadOnly="True" TextWrapping="Wrap" MaxHeight="200" />
<ScrollViewer VerticalScrollBarVisibility="Auto" MaxHeight="200" Width="200" Background="White">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}"
FontSize="24"
Foreground="Black"
IsChecked="{Binding IsChecked, Mode=TwoWay}"
IsThreeState="False" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Grid>
CustomUserControl Cs file
public sealed partial class CustomComboBox : UserControl
{
public CustomComboBox()
{
this.InitializeComponent();
}
public ObservableCollection<Item> DropDownDataSource
{
get { return (ObservableCollection<Item>)GetValue(DropDownDataSourceProperty); }
set { SetValue(DropDownDataSourceProperty, value); }
}
public static readonly DependencyProperty DropDownDataSourceProperty =
DependencyProperty.Register("DropDownDataSource", typeof(ObservableCollection<Item>), typeof(CustomComboBox), new PropertyMetadata("", HasDropDownItemUpdated));
private static void HasDropDownItemUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomComboBox ucrcntrl)
{
var grd = UIElementExtensions.FindControl<Grid>(ucrcntrl, "GrdMainContainer");
grd.DataContext = ucrcntrl.DropDownDataSource as ObservableCollection<Item>;
}
}
}
All looks good to me, but for some reason, Dropdown is coming empty. Instead of the dependency property, If I assign a view model directly to the Control it works fine. But in my condition, it is mandatory that I have properties like DataSource,SelectedIndex, etc on the user control for the end-user to use. Can anyone point out what is going wrong here?
Here, I have attached a copy of my complete code.
I downloaded your sample code, the problem should be in the binding.
<ItemsControl ItemsSource="{Binding Items}">
This way of writing is not recommended. In the ObservableCollection, Items is a protected property and is not suitable as a binding property.
You can try to bind dependency property directly in ItemsControl:
<ItemsControl ItemsSource="{x:Bind DropDownDataSource,Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<CheckBox IsChecked="{x:Bind IsChecked, Mode=TwoWay}"
IsThreeState="False" >
<TextBlock Text="{x:Bind Text}" Foreground="Black" FontSize="24"/>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In addition, you may have noticed that I modified the style of CheckBox and rewritten the content to TextBlock, because in the default style of CheckBox, Foreground is not bound to the internal ContentPresenter.
Thanks.
I have been struggling with this for a day or so, can't figure out what I'm doing wrong here. I want to be able to select any tab in my observable collection of tabs, and I want my selection to be visible in the UI. I have tried SelectedIndex and SelectedItem. I can see that my Properties are set but my tabs are not selected, nothing happens in the UI. Here is my code:
MainWindow.xaml
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:WpfApplication5"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<ViewModel xmlns="clr-namespace:WpfApplication5" />
</Window.DataContext>
<StackPanel>
<Button Content="Select Tab Index 0" Click="Button_Click_0"/>
<Button Content="Select Tab Index 1" Click="Button_Click_1"/>
<Label Content="{Binding SelectedIndex, UpdateSourceTrigger=PropertyChanged}" />
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding SelectedIndex, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<uc:TabContent Content="{Binding Content}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_0(object sender, RoutedEventArgs e)
{
var viewModel = (ViewModel)DataContext;
viewModel.SelectedIndex = 0;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
var viewModel = (ViewModel)DataContext;
viewModel.SelectedIndex = 1;
}
}
ViewModel.cs
class ViewModel
{
private int _selectedIndex = 0;
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Tab> _tabCollection = new ObservableCollection<Tab>();
public ViewModel()
{
Tabs.Add(new Tab { Header = "Tab1", Content = new WpfApplication5.TabContent() });
Tabs.Add(new Tab { Header = "Tab2", Content = new WpfApplication5.TabContent() });
}
public ObservableCollection<Tab> Tabs
{
get { return _tabCollection; }
}
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
NotifyPropertyChanged("SelectedIndex");
}
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Tab.cs
class Tab
{
public UserControl Content { get; set; }
public string Header { get; set; }
}
TabContent.xaml
<Grid>
<Label Content="Hello World!" />
</Grid>
Your ViewModel class doesn't implement the INotifyPropertyChanged interface:
class ViewModel : INotifyPropertyChanged
{
...
That's your issue.
I implemented a TabControl with Closable TabItems in my App. For this, I am using a Collection which I fill with the SubMenuItems of MenuItem "Öffne", which are bound to ICommands in the MainViewModel.
So if I click on MenuItem "Open Tab1", then the header of Tab 1 is created, but I can not see any content. The content of the TabItem is shown AFTER I click on the Header of the TabItem. But I want it to be shown directly when the TabItem is "created" without any need of clicking on the header. Closing the TabItems from the "X" button works fine.
I looked at a couple of examples and tried a ContentTemplate, but it didn't work (Maybe I made something wrong?).
I Hope you can tell me what i have done wrong or show me a good example.
Thanks in advance!
Here are my code snippets:
MainWindow.XAML:
<Window.Resources>
<vm:MainViewModel x:Key="viewModel"/>
</Window.Resources>
<TabControl Background="#FFE5E5E5" ItemsSource="{Binding TabControlViews}" SelectedItem="{Binding CurrentTabItem}" Margin="0,21,0,0">
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel Width="120">
<TextBlock Text="{Binding Header}"/>
<Button
Command="{Binding ParameterizedCommand, Source={StaticResource viewModel}}"
CommandParameter="{Binding Header, RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
FontFamily="Courier"
FontSize="9"
FontWeight="Bold"
Margin="0,1,0,0"
Padding="0"
VerticalContentAlignment="Bottom"
Width="16" Height="16" />
<ContentPresenter
Content="{Binding Path=DisplayName}"
VerticalAlignment="Center" />
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<!--<TabControl.ContentTemplate>
<DataTemplate>
</DataTemplate>
</TabControl.ContentTemplate>-->
<TabControl.Resources>
<DataTemplate x:Name="test" DataType="{x:Type vm:MenueVM}">
<cu:MenueSearch/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:FieldPointsVM}">
<cu:FieldPointsSearch/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:DataTransferVM}">
<cu:DataTransfer/>
</DataTemplate>
</TabControl.Resources>
</TabControl>
MainWindow.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = (MainViewModel)Resources["viewModel"];
this.DataContext = vm;
}
}
MainViewModel.cs:
public MainViewModel()
{
TabControlViews = new ObservableCollection<BaseViewModel>();
_menueVM = new MenueVM("Menüpunkte", "Menue");
_fieldVM = new FieldPointsVM("Feldpunkte", "FieldPoint");
_dataVM = new DataTransferVM("DatenTransfer", "DataTransfer");
ParameterizedCommand = new RelayCommand(DoParameterizedCommand);
}
private void DoParameterizedCommand(object parameter)
{
if (parameter.ToString() == "App.ViewModel.MenueVM")
{
TabControlViews.Remove(_menueVM);
}
else if (parameter.ToString() == "App.ViewModel.FieldPointsVM")
{
TabControlViews.Remove(_fieldVM);
}
else if (parameter.ToString() == "App.ViewModel.DataTransfer")
{
TabControlViews.Remove(_dataVM);
}
}
private ICommand _parameterizedCommand;
public ICommand ParameterizedCommand
{
get
{
return _parameterizedCommand;
}
set
{
_parameterizedCommand = value;
}
}
private TabItem _propCurrentTabItem;
public TabItem CurrentTabItem
{
get
{
return _propCurrentTabItem;
}
set
{
_propCurrentTabItem = value;
}
}
private ObservableCollection<BaseViewModel> _TabControlViews = new ObservableCollection<BaseViewModel>();
public ObservableCollection<BaseViewModel> TabControlViews
{
get
{
return _TabControlViews;
}
set
{
_TabControlViews = value;
OnPropertyChanged();
}
}
public ICommand OpenMenupunkteCommand
{
get
{
return new BaseCommand(OpenMenuPunkte);
}
}
public ICommand OpenFeldpunkteCommand
{
get
{
return new BaseCommand(OpenFeldpunkte);
}
}
public ICommand OpenDataTransferCommand
{
get
{
return new BaseCommand(OpenDataTransfer);
}
}
private void OpenMenuPunkte()
{
if (!TabControlViews.Contains(_menueVM))
{
TabControlViews.Add(_menueVM);
}
}
private void OpenFeldpunkte()
{
if (!TabControlViews.Contains(_fieldVM))
{
TabControlViews.Add(_fieldVM);
}
}
private void OpenDataTransfer()
{
if (!TabControlViews.Contains(_dataVM))
{
TabControlViews.Add(_dataVM);
}
}
MenueVM.cs
public class MenueVM : BaseViewModel
{
public MenueVM()
{
//Here are some actions done for Data, but I think they are unimportant for this question
}
public MenueVM(string header, string content)
{
Header = header;
Content = content;
}
private string _header;
public string Header
{
get
{
return _header;
}
set
{
_header = value;
}
}
Still time to post an answer?
Try this :
XAML:
<TabControl ItemsSource="{Binding....}" IsSynchronizedWithCurrentItem="True">
<!-- style, template, ... -->
</TabControl>
CS:
//Adding your viewModel to your ObservableCollection<> TabControlViews
TabControlViews.Add(_viewModelToAdd);
ICollectionView collectionView = CollectionViewSource.GetDefaultView(TabControlViews);
if (collectionView != null)
{
collectionView.MoveCurrentTo(_viewModelToAdd);
//And this is because you don't want your tabItem to be selected :
collectionView.MoveCurrentToPrevious();
}
Found in the downloadable DemoMVVMApp here : https://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090030
I've also spent a huge amount of time to solve this problem... ;-)
The problem is that your tab is been created, but it´s not been selected. So, in addition to calling
TabControlViews.Add(_dataVM)
, you should also update your CurrentTabItem
CurrentTabItem = _dataVM;
and bind your TabControl SelectedItem property to your CurrentTabItem, like this:
<TabControl ItemsSource="{Binding TabControlViews}" SelectedItem="{Binding CurrentTabItem}">
Also, if you remove a TabItem and want to get back to the last one, you have to call
CurrentTabItem = TabControlViews.LastOrDefault();
This is a silverlight application, Im using asynchronous filtering for an autocompletebox, the problem is that so far i fail to bind FilterAsyncCommand property from the behaviour with the corresponding ViewModel property.
Following is the xaml declaration for the control in the View:
<controls:ChildWindow x:Class="MyApp.Views.View1"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:fw ="clr-namespace:NetBoxSys.Views"
...
>
<sdk:AutoCompleteBox
MinimumPrefixLength="3" MinimumPopulateDelay="150"
SelectedItem="{Binding Path=...}" ItemsSource="{Binding Path=...}" >
<i:Interaction.Behaviors>
<fw:FilterAsyncBehavior FilterAsyncCommand="{Binding Path=FilterAsyncCommand}" />
</i:Interaction.Behaviors>
</sdk:AutoCompleteBox>
</controls:ChildWindow>
...ViewModel code:
private ICommand filterAsyncCommand;
public ICommand FilterAsyncCommand {
get { return filterAsyncCommand; }
set {
filterAsyncCommand = value;
this.OnPropertyChanged( "FilterAsyncCommand" );
}
}
...and this is how im loading the View
var view = new View1();
view.DataContext = new ViewModel1();
view.Show(); //Modal
I have tried this syntax too but does not work either:
<fw:FilterAsyncBehavior FilterAsyncCommand="{Binding FilterAsyncCommand}" />
Need advice for this kind of binding.
UPDATE:
Behaviour code:
public class FilterAsyncBehavior : Behavior<AutoCompleteBox>
{
public ICommand FilterAsyncCommand
{
get
{
return (ICommand)GetValue(FilterAsyncCommandProperty);
}
set
{
SetValue(FilterAsyncCommandProperty, value);
}
}
public static readonly DependencyProperty FilterAsyncCommandProperty = DependencyProperty.Register("FilterAsyncCommand",
typeof(ICommand),
typeof(FilterAsyncBehavior),
new PropertyMetadata(null));
protected override void OnAttached()
{
base.OnAttached();
// handle the populating event of the associated auto complete box
AssociatedObject.Populating += AssociatedObject_Populating;
}
protected override void OnDetaching()
{
// detach the event handler
AssociatedObject.Populating -= AssociatedObject_Populating;
base.OnDetaching();
}
private void AssociatedObject_Populating(object sender, PopulatingEventArgs e)
{
// get the command
ICommand filterCommand = FilterAsyncCommand;
if (filterCommand != null)
{
// create the parameters for the command
var parameters = new FilterAsyncParameters(AssociatedObject.PopulateComplete, e.Parameter);
// execute command
filterCommand.Execute(parameters);
// cancel the population of the auto complete box
e.Cancel = true;
}
}
}
ViewModel code:
public class ViewModel1 : ViewModel, IViewModel {
public ViewModel1() {
//Initializing the command in constructor
FilterAsyncCommand = new DelegateCommand<FilterAsyncParameters>( ExecuteFilterAsync );
}
private void ExecuteFilterAsync( FilterAsyncParameters args ) {
....
}
private ICommand filterAsyncCommand;
public ICommand FilterAsyncCommand {
get { return filterAsyncCommand; }
set {
filterAsyncCommand = value;
this.OnPropertyChanged( "FilterAsyncCommand" );
}
}
}
I find the solution for my problem. I give up with passing the ViewModel at runtime, so i changed to the following implementation of the MVVM pattern
<controls:ChildWindow x:Class="MyApp.Views.View1"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:fw ="clr-namespace:NetBoxSys.Views"
...
>
<controls:ChildWindow.Resources>
<viewModel:ViewModelRecepcionOCViewModelLocator x:Key="viewModelLocator"/>
</controls:ChildWindow.Resources>
<controls:ChildWindow.DataContext>
<Binding Source="{StaticResource viewModelLocator}" Path="ViewModel" />
</controls:ChildWindow.DataContext>
<sdk:AutoCompleteBox HorizontalAlignment="Stretch" Margin="2" x:Name="remitente_Autocomplete" VerticalAlignment="Center" Grid.Column="1" Grid.Row="4"
MinimumPrefixLength="3" MinimumPopulateDelay="150" Padding="0" Height="23" TabIndex="1" Text="{Binding Path=NombreRemitente,Mode=TwoWay}"
SelectedItem="{Binding Path=SelectedRemitente,Mode=TwoWay}" ItemsSource="{Binding Path=Remitentes}" >
<i:Interaction.Behaviors>
<fw:FilterAsyncBehavior FilterAsyncCommand="{Binding Source={StaticResource viewModelLocator},Path=ViewModel.FilterAsyncCommand}" />
</i:Interaction.Behaviors>
</sdk:AutoCompleteBox>
</controls:ChildWindow>
The important part here is the binding definition for FilterAsyncCommand.
And RecepcionOCViewModelLocator is defined like this
public class RecepcionOCViewModelLocator : ViewModelLocatorBase<IRecepcionOCViewModel>
{
public IRecepcionOCViewModel ViewModel{get;set;}
}
First: I am new to MVVM and WPF.
I am trying to create a little application with a tabbed user interface. Users can create products and storage locations, using a button which should open a new TabItem.
My code in the view looks like this:
<TabControl ItemsSource="{Binding Workspaces}"
IsSynchronizedWithCurrentItem="True"
Margin="3"
DockPanel.Dock="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
and the View Model is this:
ObservableCollection<WorkspaceViewModel> _workspaces;
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceViewModel>();
}
return _workspaces;
}
set
{
_workspaces = value;
}
}
public void AddProduct(object obj)
{
Workspaces.Add(new ProductViewModel());
}
Various other buttons add different ViewModels to the Workspaces Collection.
I have defined multiple Data Template (one for each ViewModel). Here is one:
<DataTemplate DataType="{x:Type vm:ProductViewModel}">
<vw:ProductView />
</DataTemplate>
The WorkspaceViewModel is this:
namespace Inventory.Desktop.ViewModels
{
public abstract class WorkspaceViewModel : INotifyPropertyChanged
{
#region Events and EventHandlers
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
and eg the ProductViewModel
namespace Inventory.Desktop.ViewModels
{
public class ProductViewModel: WorkspaceViewModel
{
private Product _product;
private string _displayName;
public string DisplayName
{
get
{
if (String.IsNullOrEmpty(_displayName))
{
return "Neues Produkt";
} else
{
return _displayName;
}
}
set
{
_displayName = value;
NotifyPropertyChanged("DisplayName");
}
}
#region Public Properties
public Product Product
{
get
{
return _product;
}
set
{
_product = value;
NotifyPropertyChanged("Product");
}
}
public string Title
{
get
{
return _product.Title;
}
set
{
_product.Title = value;
NotifyPropertyChanged("Title");
}
}
public string ScanCode
{
get
{
return _product.ScanCode;
}
set
{
_product.ScanCode = value;
NotifyPropertyChanged("ScanCode");
}
}
public string Manufacturer
{
get
{
return _product.Manufacturer;
}
set
{
_product.Manufacturer = value;
NotifyPropertyChanged("Manufacturer");
}
}
public string ManufacturerNumber
{
get
{
return _product.ManufacturerNumber;
}
set
{
_product.ManufacturerNumber = value;
NotifyPropertyChanged("ManufacturerNumber");
}
}
public string Description
{
get
{
return _product.Description;
}
set
{
_product.Description = value;
NotifyPropertyChanged("Description");
}
}
#endregion
#region Commands
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
return _saveCommand;
}
set
{
_saveCommand = value;
}
}
#endregion
#region Command Executions
public void Save(object obj)
{
using (var db = new InvContext())
{
db.Products.Attach(Product);
db.Entry(Product).State = Product.ProductId == 0 ?
EntityState.Added : EntityState.Modified;
db.SaveChanges();
}
MessageBox.Show("Product saved: " + Product.Title);
}
#endregion
#region Constructors
public ProductViewModel()
{
if (_product == null)
{
_product = new Product();
}
SaveCommand = new RelayCommand(new Action<object>(Save));
}
#endregion
}
}
Here the ProductView.xaml view:
<UserControl x:Class="Inventory.Desktop.Views.ProductView"
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="400" d:DesignWidth="450">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" FlowDirection="RightToLeft">
<Button Name="SaveProductButton" Command="{Binding SaveCommand}" Content="Speichern" Margin="3" BorderThickness="0">
</Button>
</StackPanel>
<StackPanel DockPanel.Dock="Top" VerticalAlignment="Stretch">
<Label Content="Scan Code" />
<TextBox Text="{Binding Path=ScanCode}" HorizontalAlignment="Stretch" Margin="3" Padding="3" Height="50" TextAlignment="Right">
<TextBox.Background>
<ImageBrush ImageSource="..\Images\Barcode32.png" AlignmentX="Left" Stretch="None" />
</TextBox.Background>
</TextBox>
<Label Content="Bezeichnung" />
<TextBox Text="{Binding Path=Title, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Hersteller" />
<TextBox Text="{Binding Path=Manufacturer, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Hersteller Nummer" />
<TextBox Text="{Binding Path=ManufacturerNumber, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Beschreibung / Information" />
<TextBox Text="{Binding Path=Description, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
</StackPanel>
</DockPanel>
</UserControl>
and here the code-behind ProductView.xaml.cs:
namespace Inventory.Desktop.Views
{
/// <summary>
/// Interaktionslogik für ProductView.xaml
/// </summary>
public partial class ProductView : UserControl
{
ProductViewModel _productModel = new ProductViewModel();
public ProductView()
{
InitializeComponent();
base.DataContext = _productModel;
}
}
}
What's currently working:
When I click a button, I got a new TabItem displaying the correct view and all commands work correctly.
What's not working:
When I open a TabItem, enter some information, and then I open another TabItem with a different ViewModel, switching the focus to the new TabItem and then back to the original oen, then all entered information are gone (object is null).
When I open a TabItem, enter some information, and then I open another TabItem with the same ViewModel, then both TabItems show the the same information.
When I add a new TabItem, it doesn't get focus.
I am totally lost and I hope you can tell me what I am doing wrong.
Best
Stefan
Have a property on your ViewModel to store the reference to current/selected tab
public WorkspaceViewModel SelectedTab
{
get { return _selectedTab; }
set
{
_selectedTab = value;
RaisePropertyChanged(() => SelectedTab);
}
}
and bind this to SelectedItem property on TabControl.
<TabControl ItemsSource="{Binding Workspaces}"
SelectedItem="{Binding SelectedTab, Mode=TwoWay}"
Margin="3"
DockPanel.Dock="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
And finally, you want to update SelectedTab property whenever you are adding a new tab. Modify your AddProduct like this:
public void AddProduct(object obj)
{
var workspace = new ProductViewModel();
Workspaces.Add(workspace);
SelectedTab = workspace;
}