Show User Control View inside Tab Item when Tab Item is selected - c#

I have created a couple of UserControl views and now I want to show the corresponding view when a tab item is clicked. So one tab item gets one view. I would like to do this in MVVM but don't know how.
Please take a look at the following code and give me some advice on how to achieve that:
The MainView (with the TabControl only):
...
<TabControl Name="pnlFormButtons"
Margin="25"
Background="Black"
SelectedItem="{Binding SelTab}"
>
<TabItem Name="tabInventurartikel" Header="Inventurartikel hinzufügen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
>
</TabItem>
<TabItem Name="tabSonderartikel" Header="Sonderartikel hinzufügen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2">
</TabItem>
<TabItem Name="tabAnlegen" Header="Lieferschein anlegen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
<TabItem Name="tabDrucken" Header="Lieferschein drucken"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
<TabItem Name="tabHilfeseite" Header="Hilfeseite aufrufen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
<TabItem Name="tabFehlerMelden" Header="Fehler bzw. Bug melden"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
</TabControl>
...
The MainViewModel (only relevant code):
...
//Binding Property SelTab - It binds to the selected tab item
private string _selTab;
public string SelTab
{
get { return _selTab; }
set
{
_selTab = value;
OnPropertyChanged("SelTab"); //INotifyPropertyChanged
GetSelTab(); //check which tab item is selected and display the corresponding view
}
}
public void GetSelTab()
{
UserControl usc = null; //initialize user control object
switch(SelTab) //which tab item is selected?
{
case "tabInventurartikel": // = TabControl.SelectedItem
usc = new Inventurartikel(); //Initialize (Show) Inventurartikel.xaml
SelTab.Content = usc; //Here I don't know how to actually show the view in the tab item because SelectedItem.Content does not exist...
break;
case "tabSonderartikel":
usc = new neuerArtikel(); //same problem here...
break;
default:
break;
}
}
...
NOTE:
The views for the tab items are basically just user control forms that I want to show inside the tab item when the corresponding tab item is selected.
I shouldn't post them here because I want to keep the focus on the actual problem as simple and as clear as possible. Any help is highly appreciated!

The easiest solution would be to bind the tab control's item source to a list of view models. Then, if you add/remove view models, tabs are added/removed accordingly.
Main window xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="CustomHeaderTemplate">
<Label Content="{Binding TabName}" />
</DataTemplate>
</Grid.Resources>
<TabControl x:Name="tbCtrl" ItemsSource="{Binding Items}" Loaded="tbCtrl_Loaded" SelectionChanged="tbCtrl_SelectionChanged" ItemTemplate="{StaticResource CustomHeaderTemplate}">
<TabControl.ContentTemplate>
<DataTemplate>
<uc:DeviceTab/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
The important thing is the binding of ItemSource.
Tab control view model:
class TabControlViewModel
{
public ObservableCollection<ItemViewModel> Items { get; } = new ObservableCollection<ItemViewModel>();
}
Tab control code behind Loaded event. Here you can add view models and the tab control sets up the tabs accordingly:
private void tbCtrl_Loaded(object sender, RoutedEventArgs e)
{
var tabControlViewModel = new TabControlViewModel();
tabControlViewModel.Items.Add(new ItemViewModel());
DataContext = tabControlViewModel;
tbCtrl.SelectedIndex = 0;
}
This only works if all tabs are the same. There's also a solution if you need different user controls for each tab. In that case, you need to specify a data template for the tab item's content. Basically you can tell it to load user controls based on the type of the view model. Unfortunately I don't know how to do that, but I've seen examples for it. I know it's not the exact answer you need, but I hope it helps!

Related

How to know when clicked on currently active tab item wpf?

Assume there are tab1 and tab2 as two tab items and currently active tab is tab1. SelectedIndex Property set is only called when there is a change in SelectedIndex. I want an which executes even on currently active tab. MouseDown event is not working on tab item.
Any other solutions?
I suggest you to make a Tab HeaderTemplate to handle the click. Look at the sample code below:
<Window.Resources>
<DataTemplate x:Key="myTabHeaderTemplate">
<!-- Handle click here -->
<Grid MouseDown="TabControl_MouseDown">
<TextBlock Text="{Binding}" Margin="5" />
</Grid>
</DataTemplate>
</Window.Resources>
<TabControl
Background="WhiteSmoke"
SelectedItem="{Binding CurrentTab}">
<TabItem Header="tab 1" Background="LightPink" HeaderTemplate="{StaticResource myTabHeaderTemplate}" />
<TabItem Header="tab 2" Background="LightGreen" HeaderTemplate="{StaticResource myTabHeaderTemplate}" />
</TabControl>
TabControl_MouseDown() will be invoked even if your tab is already selected
You have to implement this method in code behind:
private void TabControl_MouseDown(object sender, MouseButtonEventArgs e){ ... }
Or you can bind it to a command in your ViewModel...

WPF: Ordering and Displaying ListView SelectedItems in MVVM

I have a ListView with SelectionMode=Multiple and two TextBoxes. One should display the top-most selected item, one should display the bottom most selected item. I am also working using the MVVM design pattern.
The issues are as follows:
"SelectedItems" is indexed from the first selection point - so the SelectedItems[0] can be the bottom-most selected item, which is undesirable. I want the top-most item to display in the top box and the bottom-most item to display in the bottom box.
I can't seem to reference SelectedItems[ SelectedItems.Count - 1] from the XAML in order to display the last selected item.
Here's a look at my current XAML:
<ListView x:Name="myListView" ItemsSource="{Binding MyList}"
SelectionMode="Multiple">
<TextBox x:Name="topTextBox" Grid.Column="1" Grid.Row="2"
Text="{Binding ElementName=myListView, Path=SelectedItems[0].ID}" />
<TextBox x:Name="bottomTextBox" Grid.Column="1" Grid.Row="3"
Text="{Binding ElementName=myListView, Path=SelectedItems[SelectedItems.Count-1].ID}" />
I'm not sure what the best approach to take is.
The solution I settled on is as follows:
I used the interactive namespace (Don't forget to reference the DLL) and the following XAML:
xmlns:interact="http://schemas.microsoft.com/expression/2010/interactivity"
<TextBox x:Name="startTextBox" Grid.Column="1" Grid.Row="2"
Text="{Binding LatestSelectedItem}" />
<ListView x:Name="myListView" ItemsSource="{Binding MyList}"
SelectionMode="Extended" >
<interact:Interaction.Triggers>
<interact:EventTrigger EventName="MouseUp"> <!-- Alternatively, OnSelectionChanged -->
<interact:InvokeCommandAction Command="{Binding MyListViewSelectionChangedCommand}"
CommandParameter="{Binding ElementName=myListView, Path=SelectedItems}" />
</interact:EventTrigger>
</interact:Interaction.Triggers>
</ListView>
Then I provided appropriate Properties in my ViewModel, along with the following command:
private void MyListView_SelectionChanged(object param)
{
IList selectedItems = (IList)param;
List<MyViewModel> myList = selectedItems.OfType<MyViewModel>().ToList();
if (myList.Count > 0)
{
myList.Sort(); // Implement comparator on MyViewModel
LatestSelectedItem = myList[myList.Count-1];
}
}
Works fine.

How to add controls dynamically to a UserControl through user's XAML?

I want to create a user control that contains a TextBlock and a StackPanel that will allow the user to add his/her own controls to the user control dynamically in XAML.
Here is the sample XAML for my UserControl:
<UserControl x:Class="A1UserControlLibrary.UserControlStackPanel"
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="200" d:DesignWidth="300">
<StackPanel>
<TextBlock Text="I want the user to be able to add any number of controls to the StackPanel below this TextBlock."
FontFamily="Arial" FontSize="12" FontWeight="DemiBold" Margin="5,10,5,10" TextWrapping="Wrap"/>
<StackPanel>
<!-- I want the user to be able to add any number of controls here -->
</StackPanel>
</StackPanel>
</UserControl>
I would like the user to be able to embed this user control in their XAML and add their own controls to the stack panel of the user control:
<uc:A1UserControl_StackPanel x:Name="MyUserControl_Test" Margin="10" Height="100">
<Button Name="MyButton1" Content="Click" Height="30" Width="50"/>
<Button Name="MyButton2" Content="Click" Height="30" Width="50"/>
<Button Name="MyButton3" Content="Click" Height="30" Width="50"/>
</uc:A1UserControl_StackPanel>
Doing this using the above XAML does not work. Any ideas?
You can do that, although not quite like your example. You need two things. The first is to declare a DependencyProperty of type UIElement, of which all controls extend:
public static DependencyProperty InnerContentProperty = DependencyProperty.Register("InnerContent", typeof(UIElement), typeof(YourControl));
public UIElement InnerContent
{
get { return (UIElement)GetValue(InnerContentProperty); }
set { SetValue(InnerContentProperty, value); }
}
The second is to declare a ContentControl in the XAML where you want the content to appear:
<StackPanel>
<TextBlock Text="I want the user to be able to add any number of controls to the StackPanel below this TextBlock."
FontFamily="Arial" FontSize="12" FontWeight="DemiBold" Margin="5,10,5,10" TextWrapping="Wrap"/>
<StackPanel>
<ContentControl Content="{Binding InnerContent, RelativeSource={RelativeSource AncestorType={x:Type YourXmlNamspacePrefix:ContentView}}}" />
</StackPanel>
</StackPanel>
In my opinion, if you use StackPanels, you could find that your content does not get displayed correctly... I'd advise you to use Grids for layout purposes for all but the simplest layout tasks.
Now the one difference to your example is in how you would use your control. The InnerContent property is of type UIElement, which means that it can hold one UIElement. This means that you need to use a container element to display more than one item, but it has the same end result:
<YourXmlNamspacePrefix:YourControl>
<YourXmlNamspacePrefix:YourControl.InnerContent>
<StackPanel x:Name="MyUserControl_Test" Margin="10" Height="100">
<Button Content="Click" Height="30" Width="50"/>
<Button Content="Click" Height="30" Width="50"/>
<Button Content="Click" Height="30" Width="50"/>
</StackPanel>
</YourXmlNamspacePrefix:YourControl.InnerContent>
</YourXmlNamspacePrefix:YourControl>
And the result:
UPDATE >>>
For the record, I know exactly what you want to do. You, it seems, do not understand what I am saying, so I'll try to explain it one last time for you. Add a Button with the Tag property set as I've already shown you:
<Button Tag="MyButton1" Content="Click" Click="ButtonClick" />
Now add a Click handler:
private void ButtonClick(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
if (button.Tag = "MyButton1") DoSomething();
}
That's all there is to it.

ComboBox Item Selection Works With Keyboard But Not Mouse

I have a combo box that is not working as I expect at runtime. I can use the mouse to expand the drop-down window, but clicking an item does not seem to select it. The dropdown goes away, but the selection is not changed. The same control seems to work as expected using the keyboard. Arrow up/down changes the selection. I can use the arrow keys to choose and enter to select to change the value as well.
How do I get clicking to select an item?
<DataTemplate DataType="{x:Type myType}">
<Border ...>
<Grid x:Name="upperLayout">
<Grid x:Name="lowerLayout">
<ComboBox x:Name="combo"
Grid.Column="2"
ItemsSource="{Binding Things}"
SelectedItem="{Binding SelectedThing}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Grid>
</Border>
</DataTemplate>
I can't really tell what's wrong from your code however, I'd strongly suggest you to use Snoop to debug your controls (http://snoopwpf.codeplex.com/)
By holding Ctrl+Shift and pointing the mouse where you ComboBox is supposed to grab the input you would instantly find out who is having the focus instead of your combo box.
You can even change the value of a property, really your best friend for debugging your templates !
EDIT
I'm afraid but the code you've posted works for me:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication6="clr-namespace:WpfApplication6"
Title="MainWindow"
Width="525"
Height="350">
<Window.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type wpfApplication6:MyType}">
<Border>
<Grid x:Name="upperLayout">
<Grid x:Name="lowerLayout">
<ComboBox x:Name="combo"
Grid.Column="0"
ItemsSource="{Binding Path=Things}"
SelectedItem="{Binding Path=SelectedThing}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type wpfApplication6:MyThing}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<Grid x:Name="grid">
<ContentControl x:Name="content" ContentTemplate="{StaticResource myTemplate}" Margin="58,79,71,40" />
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
MyType type = new MyType()
{
Things = new List<MyThing>() {new MyThing() {Name = "aaa"}, new MyThing() {Name = "bbb"}}
};
content.Content = type;
}
}
public class MyType
{
public MyThing SelectedThing { get; set; }
public List<MyThing> Things { get; set; }
}
public class MyThing
{
public string Name { get; set; }
}
Maybe something else is screwing it such as a style with no key or whatever, post more of your code you're having a problem with.
Root cause was that another developer had implemented some code that changed the focus on the preview mouse down event. This code was updated to have the desired behavior without modifying focus and the combo box now works as expected. The information needed to diagnose was not in the original question (can't publish it all...).

Silverlight TimePicker / ChildWindow / DataTemplate combination causes UI freeze

I'm working on a Silverlight project, using MVVM, and I've run into a problem that only appears to occur under some fairly specific situations. I've tried to strip everything down as much as possible so only the important parts are left.
The Scenario:
A standard Silverlight ChildWindow
The ChildWindow has a Selector Control (e.g. a ComboBox or a ListBox).
The ChildWindow has a ContentPresenter
The Content of this ContentPresenter is bound to the SelectedValue of the above mentioned Selector Control (In reality it is bound to a ViewModel property, but for testing purposes this is not necessary).
The ContentPresenter uses a DataTemplateSelector to determine it's ContentTemplate
The Selector Control contains the string values "A" and "B" which correspond to DataTemplates "TemplateA" and "TemplateB".
"TemplateB" contains a TimePicker control.
The Problem:
After selecting "B" (therefore loading "TemplateB"), the next time you try to change the selected template, the host webpage will freeze. No exception is thrown and no information is given.
Notes:
Everything works fine outside of a ChildWindow.
Everything works fine if none of the templates contain a TimePicker control.
The TimePicker control appears to work fine if it is displayed without using a ContentPresenter/DataTemplateSelector.
Ive looked at TimePicker in ChildWindow causes an exception on the Silverlight Toolkit CodePlex page. That particular issue appears to be resolved and I've tried implementing the suggested workaround just to be sure, and it has no effect.
Code to reproduce the problem:
ChildWindow XAML:
<controls:ChildWindow
x:Class="TimePickerProblem.ChildWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
xmlns:local="clr-namespace:TimePickerProblem"
Width="400"
Height="300"
Title="ChildWindow1">
<controls:ChildWindow.Resources>
<local:ViewModel
x:Key="vm" />
</controls:ChildWindow.Resources>
<Grid>
<Grid
x:Name="LayoutRoot"
Background="White"
DataContext="{StaticResource vm}">
<StackPanel
HorizontalAlignment="Center">
<ComboBox
x:Name="ComboBox"
Margin="20"
ItemsSource="{Binding Templates}" />
<ContentPresenter
Content="{Binding ElementName=ComboBox, Path=SelectedValue}">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<local:TemplateSelector
Content="{Binding}">
<local:TemplateSelector.TemplateA>
<DataTemplate>
<TextBlock
Text="Hello from A" />
</DataTemplate>
</local:TemplateSelector.TemplateA>
<local:TemplateSelector.TemplateB>
<DataTemplate>
<toolkit:TimePicker />
</DataTemplate>
</local:TemplateSelector.TemplateB>
</local:TemplateSelector>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</StackPanel>
</Grid>
<Button
x:Name="CancelButton"
Content="Cancel"
Click="CancelButton_Click"
Width="75"
Height="23"
HorizontalAlignment="Right"
Margin="0,12,0,0"
Grid.Row="1" />
<Button
x:Name="OKButton"
Content="OK"
Click="OKButton_Click"
Width="75"
Height="23"
HorizontalAlignment="Right"
Margin="0,12,79,0"
Grid.Row="1" />
</Grid>
ViewModel:
public List<string> Templates { get { return new List<string>() { "a", "b" }; } }
DataTemplateSelector:
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate TemplateA { get; set; }
public DataTemplate TemplateB { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
string value = (string)item;
switch (value.ToLower())
{
case "a":
return TemplateA;
case "b":
return TemplateB;
default:
return base.SelectTemplate(item, container);
}
}
}
I've just tried in SL5, and couldn't reproduce your problem. If I've time, I will try SL4. Do you have any styles present that might cause a problem?
If you are describing a situation where overlay appears after dismissing ChildWindow, then it is known bug. Here's a workaround.
ChildWindow w = new MyChildWindow();
w.Closed += (s, eargs) => { Application.Current.RootVisual.SetValue(Control.IsEnabledProperty, true); };
w.Show();

Categories