Non-stacking ItemsControl - wpf

There is probably a better way to do this, but here goes: I have a user control for each step of a user workflow process. I am using Microsoft Prism and dependency injection. I have an ItemsControl that is binded to a List. Anytime the user does an action that requires the next "step", I add the user control to this list, which the ItemsControl binds to.
Of course the user controls stack on top of eachother from top to bottom. What I want is to have the last one drawn be drawn ON TOP OF every other one. That way when the user is done with this step and hits "OK", I just remove the user control, and the previous step is automatically displayed.

I created a tab control, as per HighCore's idea.
<TabControl Name="MIPTabs" ItemsSource="{Binding UCList}" HorizontalAlignment="Left" VerticalAlignment="Top" Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=Height}" Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=Width}">
Each item in UCList is a UserControl, and its content gets automatically added to each new tab. I then hide the tabs with:
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
Then I use the following code so that each time a tab is added, it will set the selected tab equal to the newest tab:
public MIP(IMIPVM viewModel) : this()
{
this.DataContext = viewModel;
var dpd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(TabControl));
if (dpd != null)
{
dpd.AddValueChanged(MIPTabs, UpdateTabs);
}
}
private void UpdateTabs(object sender, EventArgs e)
{
var view = CollectionViewSource.GetDefaultView(MIPTabs.ItemsSource);
view.CollectionChanged += (o, f) =>
{
//This makes it so the last item ADDED is always the tab that is being displayed
MIPTabs.SelectedIndex = MIPTabs.Items.Count-1;
};
}

Related

Specify binding for HeaderTemplate programmatically

I'm having various problems with the virtualisation of views in the TabControl. Luckily, I've found something that I think will solve all my problems in this CodeProject article.
One problem is introduced by this solution though, and that is that it destroys my HeaderTemplate. The tab header has the same content as the content control.
My view uses a TabControl like this:
<TabControl
behaviors:TabItemGeneratorBehavior.ItemsSource="{Binding MyItems, Mode=OneWay}"
behaviors:TabItemGeneratorBehavior.SelectedItem="{Binding MySelectedItem, Mode=TwoWay}">
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter>
<ContentPresenter.Content>
<TextBlock Text="{Binding Title}"/>
</ContentPresenter.Content>
</ContentPresenter>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The behaviors:TabItemGeneratorBehavior is from the CodeProject article. Inside the TabItemGeneratorBehavior is a method that generates TabItems:
private void AddTabItem(object item)
{
var contentControl = new ContentControl();
var tab = new TabItem
{
DataContext = item,
Content = contentControl,
HeaderTemplate = _tabControl.ItemTemplate
};
contentControl.SetBinding(ContentControl.ContentProperty, new Binding());
tab.SetBinding(HeaderedContentControl.HeaderProperty, new Binding());
_tabControl.Items.Add(tab);
}
I think my problem is with the line that sets the binding for the HeaderProperty. How can I set the binding so that it uses the HeaderTemplate defined in my XAML above?
OP here.
The solution is to remove the HeaderTemplate assignment when creating the TabItem:
var tab = new TabItem
{
DataContext = item,
Content = contentControl,
// HeaderTemplate = _tabControl.ItemTemplate
};

Dynamic template for a ComboBox in ListBox

I have a ListBox with an embedded ComboBox:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox Width="100" IsEditable="False" Height="20">
<TextBlock Text="Opt#1"></TextBlock>
<TextBlock Text="Opt#2"></TextBlock>
<TextBlock Text="Opt#3"></TextBlock>
</ComboBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'd like to present the ComboBox as a simple text (e.g. TextBlock) when a ListBox row is not selected, and show it as a ComboBox when the ListBox row is selected.
I was thinking that replacing ComboBox template dynamically would do the trick. How to accomplish that?
Thanks,
Leszek
The best way to swap templates is to use the ItemTemplateSelector propery of the ListBox and set it to a class you create which inherits from DataTemplateSelector.
Here is a link that provides an example: http://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector.aspx
I would simply use a style that replace the ListBox.ItemTemplate whenever the ListBoxItem becomes selected.
Here's a quick example
<ListBox.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<TextBlock Text="{Binding }" />
</DataTemplate>
<DataTemplate x:Key="ComboBoxTemplate">
<ComboBox SelectedItem="{Binding }">
<ComboBoxItem>Opt#1</ComboBoxItem>
<ComboBoxItem>Opt#2</ComboBoxItem>
<ComboBoxItem>Opt#3</ComboBoxItem>
</ComboBox>
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template" Value="{StaticResource TextBoxTemplate}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Template" Value="{StaticResource ComboBoxTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
I'd actually suggest using IsKeyboardFocusWithin instead of IsSelected as the trigger property, because templates can let you interact with them without setting the item as selected.
Thanks Josh and Rachel for pointing me in a right direction.
I came up with a solution similar to the one suggested by Rachel. My problem was I could not make ItemTemplateSelector work and I did not know how to pass the state IsSelected from my listbox. I also could not use DataTemplate because my ListBox item is much more complex than a single element (I simplified it in my previous post for the sake of example).
Anyway, I came up with the following solution. It's not very elegant but it works:
I defined a new style in Application resources:
<Style x:Key="TextBlockTemplate" TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="{Binding}" Margin="3" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I attached SelectionChanged and PreviewMouseDown handlers to my ListBox:
I defined MyListBox_PreviewMouseDown:
private void MyListBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// Grab the selected list box item.
object element = (e.OriginalSource as FrameworkElement).DataContext;
var item = MyListBox.ItemContainerGenerator.ContainerFromItem(element)
as ListBoxItem;
// Mark the row in the ListBox as selected.
if (item != null)
item.IsSelected = true;
}
I defined MyListBox_SelectionChanged:
private ComboBox prevComboBox = null;
private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Grab the list box.
ListBox list = sender as ListBox;
// Although there could be only one item selected,
// we iterate over all selected items.
foreach (MyDataItem dat in list.SelectedItems)
{
var item = list.ItemContainerGenerator.ContainerFromItem(dat) as ListBoxItem;
// FindElement is a helper method to find an element in a visual tree.
ComboBox cbo = FindElement(item, "MyComboBox") as ComboBox;
if (cbo != prevComboBox)
{
cbo.Style = null;
if (prevComboBox != null)
prevComboBox.Style =
(Style)Application.Current.Resources["TextBlockTemplate"];
prevComboBox = cbo;
}
}
}
Thanks,
Leszek

WPF - Redrawing a Context Menu when Items change?

I have a ItemsControl in a ScrollViewer, and when the items exceed the width of the ScrollViewer they are put into a ContextMenu and shown as a DropDown instead. My problem is that when the Context Menu is first loaded, it saves the saves the size of the Menu and does not redraw when more commands get added/removed.
For example, a panel has 3 commands. 1 is visible and 2 are in the Menu. Viewing the menu shows the 2 commands and draws the control, but then if you resize the panel so 2 are visible and only 1 command is in the menu, it doesn't redraw the menu to eliminate that second menu item. Or even worse, if you shrink the panel so that no commands are shown and all 3 are in the Menu, it will only show the top 2.
Here's my code:
<Button Click="DropDownMenu_Click"
ContextMenuOpening="DropDownMenu_ContextMenuOpening">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding Path=MenuCommands}" Placement="Bottom">
<ContextMenu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Path=Command}" />
<Setter Property="Visibility" Value="{Binding Path=IsVisible, Converter={StaticResource ReverseBooleanToVisibilityConverter}}"/>
</Style>
</ContextMenu.Resources>
<ContextMenu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DisplayName}" />
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Button.ContextMenu>
</Button>
Code Behind:
void DropDownMenu_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
Button b = sender as Button;
b.ContextMenu.IsOpen = false;
e.Handled = true;
}
private void DropDownMenu_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
ContextMenu cMenu = b.ContextMenu;
if (cMenu != null)
{
cMenu.PlacementTarget = b;
cMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
cMenu.IsOpen = true;
}
}
I have tried using InvalidateVisual and passing an empty delegate on Render to try and force a redraw, however neither works. I'm using .Net 4.0.
Is MenuCommands a collection? If it is, is it an ObservableCollection?
If you bind a collection to an ItemsControl, that collection must implement INotifyCollectionChanged interface to let the ItemsControl know that the number of items in the collection has changed, so that the control can "redraw" itself.

How to get a group of toggle buttons to act like radio buttons in WPF?

I have a group of buttons that should act like toggle buttons, but also as radio buttons where only one button can be selected / pressed down at a current time. It also need to have a state where none of the buttons are selected / pressed down.
The behavior will be kind of like Photoshop toolbar, where zero or one of the tools are selected at any time!
Any idea how this can be implemented in WPF?
This is easiest way in my opinion.
<RadioButton Style="{StaticResource {x:Type ToggleButton}}" />
Enjoy!
-- Pricksaw
The easiest way is to style a ListBox to use ToggleButtons for its ItemTemplate
<Style TargetType="{x:Type ListBox}">
<Setter Property="ListBox.ItemTemplate">
<Setter.Value>
<DataTemplate>
<ToggleButton Content="{Binding}"
IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Then you can use the SelectionMode property of the ListBox to handle SingleSelect vs MultiSelect.
<RadioButton Content="Point" >
<RadioButton.Template>
<ControlTemplate>
<ToggleButton IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
it works for me, enjoy!
you could always use a generic event on the Click of the ToggleButton that sets all ToggleButton.IsChecked in a groupcontrol(Grid, WrapPanel, ...) to false with the help of the VisualTreeHelper; then re-check the sender.
Or something in the likes of that.
private void ToggleButton_Click(object sender, RoutedEventArgs e)
{
int childAmount = VisualTreeHelper.GetChildrenCount((sender as ToggleButton).Parent);
ToggleButton tb;
for (int i = 0; i < childAmount; i++)
{
tb = null;
tb = VisualTreeHelper.GetChild((sender as ToggleButton).Parent, i) as ToggleButton;
if (tb != null)
tb.IsChecked = false;
}
(sender as ToggleButton).IsChecked = true;
}
you can put grid with radiobuttons in it, and create button like template for raduiobuttons. than just programmaticaly remove check if you don't want buttons to be toggled
You can also try System.Windows.Controls.Primitives.ToggleButton
<ToggleButton Name="btnTest" VerticalAlignment="Top">Test</ToggleButton>
Then write code against the IsChecked property to mimick the radiobutton effect
private void btnTest_Checked(object sender, RoutedEventArgs e)
{
btn2.IsChecked = false;
btn3.IsChecked = false;
}
I did this for RibbonToggleButtons, but maybe it's the same for regular ToggleButtons.
I bound the IsChecked for each button to a "mode" enum value using EnumToBooleanConverter from here How to bind RadioButtons to an enum? (Specify the enum value for this button using the ConverterParameter. You should have one enum value for each button)
Then to prevent unchecking a button that's already checked, put this in your code behind for the Click event for each of the RibbonToggleButtons:
private void PreventUncheckRibbonToggleButtonOnClick ( object sender, RoutedEventArgs e ) {
// Prevent unchecking a checked toggle button - so that one always remains checked
// Cancel the click if you hit an already-checked button
var button = (RibbonToggleButton)sender;
if( button.IsChecked != null ) { // Not sure why checked can be null but that's fine, ignore it
bool notChecked = ( ! (bool)button.IsChecked );
if( notChecked ){ // I guess this means the click would uncheck it
button.IsChecked = true;
}
}
}
To help people like Julian and me (two minutes ago...). You can derive from the RadioButton like this.
class RadioToggleButton : RadioButton
{
protected override void OnToggle()
{
if (IsChecked == true) IsChecked = IsThreeState ? (bool?)null : (bool?)false;
else IsChecked = IsChecked.HasValue;
}
}
Then, you can use it like Uday Kiran suggested...
<Window x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Sample"
Title="MainWindow" Height="600" Width="600">
<StackPanel>
<local:RadioToggleButton Content="Button" Style="{StaticResource {x:Type ToggleButton}}" />
</StackPanel>
</Window>
This method allows only one ToggleButton to be Checked at a time, and it also allows UnChecking.
I took a few piece of the answers and added some extra code. Now you can have different groups of toggle buttons which act like one toggle button:
<UserControl.Resources>
<Style x:Key="GroupToggleStyle" TargetType="ToggleButton">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding GroupName, RelativeSource={RelativeSource Self}}" Value="Group1"/>
<Condition Binding="{Binding BooleanProperty}" Value="true"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="IsChecked" Value="true"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
And the different groups of radio buttons which look like toggle buttons:
<Radio Button GroupName="Group1" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group1" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group2" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group3" Style="{StaticResource {x:Type ToggleButton}}">
One simplistic implementation could be where you maintain a flag in your code behind such as:
ToggleButton _CurrentlyCheckedButton;
Then assign a single Checked event handler to all your context ToggleButtons:
private void ToggleButton_Checked(object sender, RoutedEventArgs e)
{
if (_CurrentlyCheckedButton != null)
_CurrentlyCheckedButton.IsChecked = false;
_CurrentlyCheckedButton = (sender as ToggleButton);
}
And, a single Unchecked event handler:
private void ToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
if (_CurrentlyCheckedButton == (sender as ToggleButton))
_CurrentlyCheckedButton = null;
}
This way you can have the 'zero or one' selection you desire.

How to add new user control in TabControl.ContentTemplate?

I am little stuck with adding new instances of a usercontrol in a TabControl.ContentTemplate?
My Xaml is here:
<TabControl ItemsSource="{Binding Tables}">
<TabControl.ItemTemplate>
<DataTemplate>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type uc:mytest1}">
<uc:mytest1>
</uc:mytest1>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I am binding TabControl.ItemsSource property to an ObservableCollection and in the content template I am adding a user control, but when this app runs I am getting new items as TabItems but the content page is holding same user control, but I want new user controls to be added for each new TabItem.
I am very new to the WPF and may be I am doing a very basic mistake, kindly guide me.
The ControlTemplate determines the appearance of the elements of the tab control that are not part of the individual tab items. The ItemTemplate handles the content of the individual tab items. Additionally, a TabItem is a headered content control, which means it has two content type properties Content and Header with two separate templates ContentTemplate and HeaderTemplate. In order to be able to populate the tab items using binding, you need to style the TabItem using the above properties.
Example:
<Window x:Class="Example.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Window"
Title="Window2" Height="300" Width="300">
<Window.DataContext>
<Binding ElementName="Window" Path="VM"/>
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="TabItemHeaderTemplate">
<Grid>
<TextBlock Text="{Binding Header}"/>
<Ellipse Fill="Red" Width="40" Height="40" Margin="0,20,0,0"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TabItemContentTemplate">
<Ellipse Fill="Green"/>
</DataTemplate>
<Style x:Key="TabItemContainerStyle" TargetType="TabItem">
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="HeaderTemplate"
Value="{StaticResource TabItemHeaderTemplate}"/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate"
Value="{StaticResource TabItemContentTemplate}"/>
</Style>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding Items}"
ItemContainerStyle="{StaticResource TabItemContainerStyle}"/>
</Grid>
</Window>
The code behind:
public partial class Window2 : Window
{
public TabControlVM VM { get; set; }
public Window2()
{
VM = new TabControlVM();
InitializeComponent();
}
}
And the view model classes:
public class TabControlVM
{
public ObservableCollection<TabItemVM> Items { get; set; }
public TabControlVM()
{
Items = new ObservableCollection<TabItemVM>();
Items.Add(new TabItemVM("tabitem1"));
Items.Add(new TabItemVM("tabitem2"));
Items.Add(new TabItemVM("tabitem3"));
Items.Add(new TabItemVM("tabitem4"));
}
}
public class TabItemVM
{
public string Header { get; set; }
public TabItemVM(string header)
{
Header = header;
}
}
Saurabh, When you set Template, usually DataTemplate, ControlTemplate etc, the visual elements inside these templates are reused in WPF with concept of UI Virtualization. TabControl typically displays only one item at a time, so it does not create new Visual Item for every tab item, instead it only changes that DataContext and refreshes bindings of "Selected Visual Item". Its loaded/unloaded events are fired, but the object is same always.
You can use loaded/unload events and write your code accordingly that your "Visual Element" which is your usercontrol, so that control should be stateless and is not dependent on old data. When new DataContext has applied you should refresh everything.
DataContextChanged, Loaded and Unloaded events can help you remove all dependencies on old data.
Otherwise, you an create a new TabItem manually with your UserControl as its Child and add it in TabControl instead of adding Data Items.
Adding TabItems manually will create new control for every item and in selected area different elements will appear based on selection.

Resources