IsVisibleChanged is not triggered when TabItem visibility is programatically updated - c#

I have a tab control with three tabs, two of which are have Visibility set to collapsed when the program starts, becomes visible under certain conditions, and can later become collapsed again. If the TabItem being collapsed is the currently selected Tab, it's content remains visible even though it has become collapsed.
The tabs' visibility is bound to my ViewModel, and is updated that way.
It will always be the case that I want the first tab to be activated when any of the tab's visibility changes. I've tried to make a simple code behind to handle this case, but the only time that code is hit is when my UserControl is loaded/unloads. The Handler is never invoked when the tab's visibility is updated. I tried setting the IsVisibleChanged property on both the tabcontrol and its items, but I can't get the codebeind to hit.
Here's my xaml:
<UserControl x:Class="MyNameSpace.MyControl"
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"
IsVisibleChanged="TabControl_IsVisibleChanged>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Platform.Presentation;component/Themes/MyTheme.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<TabControl x:Name="_tabControl" IsVisibleChanged="TabControl_IsVisibleChanged">
<TabItem Header="View 1" x:Name="_view1Tab" IsVisibleChanged="TabControl_IsVisibleChanged">
<local:SingleWorkspaceView/>
</TabItem>
<TabItem Header="View 2" x:Name="_view2Tab" Visibility="{Binding TabVisibility}" IsVisibleChanged="TabControl_IsVisibleChanged">
<local:WorkspaceDeploymentView/>
</TabItem>
<TabItem Header="View 3" x:Name="_view3Tab" Visibility="{Binding TabVisibility}" IsVisibleChanged="TabControl_IsVisibleChanged">
<local:TabDeploymentView/>
</TabItem>
</TabControl>
Here's my code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MyNameSpace
{
/// <summary>
/// Interaction logic for TopLevelControl.xaml
/// </summary>
public partial class TopLevelControl : UserControl
{
ApplicationViewModel _viewModel;
public TopLevelControl()
{
_viewModel = new ApplicationViewModel();
base.DataContext = _viewModel;
InitializeComponent();
}
private void TabControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
TabItem tab = sender as TabItem;
if(tab != null && (bool)e.NewValue)
{
_tabControl.SelectedIndex = 0;
}
}
}
}
Is there some reason the event is not firing?

Lets do it in MVVM way without any codebehind
xaml here we have binded SelectedIndex="{Binding TabSelectedIndex}" of TabControl
<TabControl SelectedIndex="{Binding TabSelectedIndex}">
<TabItem Header="abc">
<Button Content="ok"/>
</TabItem>
<TabItem Header="xyz" Visibility="{Binding TabVisibility}">
<Button Content="ok"/>
</TabItem>
<TabItem Header="pqr" Visibility="{Binding TabVisibility}">
<Button Content="ok"/>
</TabItem>
</TabControl>
xaml.cs
public MainWindow()
{
InitializeComponent();
DataContext =new ViewModel();
}
ViewModel
public class ViewModel: INotifyPropertyChanged
{
int tabSelectedIndex;
//this will be bound to the SelectedIndex of Tabcontrol
public int TabSelectedIndex
{
get { return tabSelectedIndex; }
set { tabSelectedIndex = value;
Notify("TabSelectedIndex");
}
}
Visibility tabVisibility;
//this will be binded to the Visibility of TabItem
public Visibility TabVisibility
{
get { return tabVisibility; }
set
{
tabVisibility = value;
//this is the logic that will set firstTab selected when Visibility will be collapsed
if (tabVisibility == Visibility.Collapsed)
{
tabSelectedIndex = 0;
Notify("TabSelectedIndex");
}
Notify("TabVisibility"); }
}
public event PropertyChangedEventHandler PropertyChanged;
void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
I hope this will help. If it will help then say MVVM Rocks :)

This works for me:
private void TabItem_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender is TabItem tabItem && tabItem.Parent is TabControl tabControl && tabItem.Visibility == Visibility.Collapsed && tabControl.SelectedItem == tabItem)
{
tabControl.SelectedItem = tabControl.Items.Cast<TabItem>().FirstOrDefault(t => t.Visibility == Visibility.Visible);
}
}

Related

WPF binding source mismatch for a MahApps.Metro.Controls.NumericUpDown inside a TabControl

I often use MahApps.Metro.Controls.NumericUpDown to manipulate numerical values from the user interface. Recently I have observed a binding behaviour so strange and surreal that completely undermined my faith in the WPF binding infrastructure.
The phenomenon occurred with a collection of view models bound to the ItemsSource of a TabControl. Within each of these view models, there is a property of type int that I bind to the Value property of a MahApps.Metro.Controls.NumericUpDown. I noticed that when I manipulate a NumericUpDown through its TextBox and leave the focus there just before I select a new tab in the TabControl, the value I have just set gets written into the NumericUpDown inside the new tab being selected. That is clearly undesirable as that tab contains a value I did not intend to overwrite just by selecting the containing tab.
This only occurs if I use the TextBox part to set the value, and not when I use the + and - buttons. Furthermore, taking the focus from the NumericUpDown prevents the bug from occurring. Hooking into the LostFocus event of the NumericUpDown, I could observe that the binding source of the respective binding had already changed to that within the view model associated with the next tab (instead of that associated with the NumericUpDown actually firing the LostFocus event), and that explains the surreal value bleed. (Additionally, it gets fired twice, as you will be able to see in the MWE.)
I could find a workaround by registering for the PreviewKeyDown event of the tab label and setting the focus to another element programmatically before the tab switch actually occurs. Still, as I said, this undermined my confidence in the WPF binding architecture. Is it a general WPF binding bug or is it specific to MahApps.Metro.Controls.NumericUpDown? What can I do to ensure such bugs do not occur in my code (apart from the amateur workaround I could come up with)?
The XAML part of the MWE:
<Window x:Class="NumericUpDownMWE.MainWindow"
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"
xmlns:mahApps="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:local="clr-namespace:NumericUpDownMWE"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TabControl
ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<TextBlock Text="Value" />
<mahApps:NumericUpDown
LostFocus="HandleLostFocus"
Value="{Binding Value}" />
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
The respective code-behind:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace NumericUpDownMWE
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ExampleViewModel();
}
private void HandleLostFocus(object sender, RoutedEventArgs e)
{
FrameworkElement? frameworkElement = sender as FrameworkElement;
BindingExpression? bindingExpression = frameworkElement?.GetBindingExpression(MahApps.Metro.Controls.NumericUpDown.ValueProperty);
string? sourceName = (bindingExpression?.ResolvedSource as ExampleItem)?.Name;
}
}
public class ExampleItem : INotifyPropertyChanged
{
private int value;
public event PropertyChangedEventHandler? PropertyChanged;
public string Name { get; }
public int Value {
get { return value; }
set {
if (value != this.value) {
this.value = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
}
public ExampleItem(string name, int value)
{
this.Name = name;
this.value = value;
}
}
public class ExampleViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public ObservableCollection<ExampleItem> Items { get; }
public ExampleViewModel()
{
this.Items = new ObservableCollection<ExampleItem>() {
new ExampleItem("Item A", 1),
new ExampleItem("Item B", 2),
new ExampleItem("Item C", 3)
};
}
}
}

C# WPF UI unresponsive when performing drag drop using user control

I am stuck with a C# wpf drag drop issue. I have created a very simple project that includes a User Control, a couple of classes to hold the data and a form to host multiple copies of the user control (using a bound ItemsControl). When I drag the control onto the form the drag drop is triggered, the observablecollection is updated but the UI doesn't reflect the change and future events don't seem to be working. Rolling over the add item button doesn't even show the rollover effect. Sure I am doing something stupid but I can't seem to see what it is.
Code below (mainly from Microsoft Example)
SimpleDataClass
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DragDropControl.Model
{
public class SimpleDataClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _groupName = string.Empty;
private ObservableCollection<SimpleSubDataClass> _titles = new ObservableCollection<SimpleSubDataClass>();
public string GroupName
{
get { return _groupName; }
set
{
if (_groupName != value)
{
_groupName = value;
RaisePropertyChangedEvent("GroupName");
}
}
}
public ObservableCollection<SimpleSubDataClass> Titles
{
get { return _titles; }
set
{
if (_titles != value)
{
_titles = value;
RaisePropertyChangedEvent("Titles");
}
}
}
private void RaisePropertyChangedEvent(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
SimpleSubDataClass
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DragDropControl.Model
{
public class SimpleSubDataClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _title = string.Empty;
public string Title
{
get { return _title; }
set
{
if (_title != value)
{
_title = value;
RaisePropertyChanged("Title");
}
}
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public SimpleSubDataClass(string title)
{
Title = title;
}
}
}
DDControl - XAML
<UserControl x:Class="DragDropControl.DDControl"
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:local="clr-namespace:DragDropControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="CurrentControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Name="txtGroupName" Grid.Row="0" Text="{Binding ElementName=CurrentControl, Path=ThisData.GroupName}"/>
<ListBox Name="lstTitles" Grid.Row="1" ItemsSource="{Binding ElementName=CurrentControl, Path=ThisData.Titles}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Name="lblTitle" Content="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
DDControl - Code behind
using DragDropControl.Model;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace DragDropControl
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class DDControl : UserControl
{
public static readonly DependencyProperty ThisDataProperty = DependencyProperty.Register( "ThisData",
typeof(SimpleDataClass),
typeof(DDControl),
new PropertyMetadata(new SimpleDataClass()));
public SimpleDataClass ThisData
{
get { return (SimpleDataClass)GetValue(ThisDataProperty); }
set { SetValue(ThisDataProperty, value); }
}
public DDControl()
{
InitializeComponent();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
DataObject data = new DataObject(this.ThisData);
DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
}
}
protected override void OnGiveFeedback(GiveFeedbackEventArgs e)
{
base.OnGiveFeedback(e);
if (e.Effects.HasFlag(DragDropEffects.Move))
Mouse.SetCursor(Cursors.Pen);
e.Handled = true;
}
}
}
MainWindow - Xaml
<Window x:Class="DragDropUserControlWithTextBox.MainWindow"
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"
xmlns:local="clr-namespace:DragDropUserControlWithTextBox"
xmlns:ddc="clr-namespace:DragDropControl;assembly=DragDropControl"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<StackPanel Name="stkMain" Background="Gray" Orientation="Horizontal" Drop="stkMain_Drop" AllowDrop="true">
<ItemsControl Name="icColumns" Background="Red">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Name="stkItemsControlPanel" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ddc:DDControl Background="{x:Null}" ThisData="{Binding}"/><!-- MouseMove="DDControl_MouseMove" GiveFeedback="DDControl_GiveFeedback"/>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Name="btnAddData" Content="Add Data" Click="btnAddData_Click"/>
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
MainWindow - Code behind
using DragDropControl.Model;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace DragDropUserControlWithTextBox
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<SimpleDataClass> _data = new ObservableCollection<SimpleDataClass>();
public MainWindow()
{
InitializeComponent();
CreateTestData();
}
private void CreateTestData()
{
SimpleDataClass tempSDC1 = new SimpleDataClass();
tempSDC1.GroupName = "First Item";
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_1"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_2"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_3"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_4"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_5"));
SimpleDataClass tempSDC2 = new SimpleDataClass();
tempSDC2.GroupName = "Second Item";
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_1"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_2"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_3"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_4"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_5"));
_data.Add(tempSDC1);
_data.Add(tempSDC2);
this.icColumns.ItemsSource = _data;
}
private void stkMain_Drop(object sender, DragEventArgs e)
{
if (e.Handled == false)
{
if (e.Data.GetDataPresent(typeof(SimpleDataClass)))
{
SimpleDataClass tempData = (SimpleDataClass)e.Data.GetData(typeof(SimpleDataClass));
_data.Add(tempData);
}
e.Effects.HasFlag(DragDropEffects.None);
Mouse.SetCursor(Cursors.Arrow);
e.Handled = true;
}
}
private void btnAddData_Click(object sender, RoutedEventArgs e)
{
SimpleDataClass tempData = new SimpleDataClass();
tempData.GroupName = "Amazing Test";
tempData.Titles.Add(new SimpleSubDataClass("AT_1"));
tempData.Titles.Add(new SimpleSubDataClass("AT_2"));
tempData.Titles.Add(new SimpleSubDataClass("AT_3"));
_data.Add(tempData);
}
}
}
I swapped to using the method described in this WPF tutorial on drag and drop. It still had an issue when you dragged and dropped a user control with a textbox on it (would assume it would be for any control that is hitenabled) where it would create a second instance of the item being dragged but that is pretty easy to work around, just set the enabled state to false when detecting the control is about to be dragged and enable it again when it is dropped. Possibly a hack but one that works.
For drag and drop, create a Behavior and handle the events in the Behavior for the avoiding the UI thread freeze scenarios. Check the below link will be useful for your scenario.
https://www.telerik.com/blogs/adding-drag-and-drop-to-wpf-listboxes-thanks-telerik!

How to close a window from the user control inside?

I've got a WPF program and at some point I open a window:
public object testCommand()
{
Window window = new Window
{
Title = "My User Control Dialog",
Content = new OKMessageBox("Error Message"),
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = ResizeMode.NoResize
};
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
window.ShowDialog();
return null;
}
User Control XAML:
<UserControl x:Class="SolidX.Base.MessageBoxes.OKMessageBox"
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:local="clr-namespace:SolidX.Base.MessageBoxes"
xmlns:viewProperties="clr-namespace:AppResources.Properties;assembly=AppResources"
mc:Ignorable="d"
d:DesignHeight="150" d:DesignWidth="400">
<Grid>
<TextBlock x:Name="textMessage" Margin="10"/>
<Grid Height="Auto" VerticalAlignment="Bottom">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<CheckBox Content="Don't show this again" HorizontalAlignment="Right" Margin="5,5,10,5"/>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="btnOk" Content="{x:Static viewProperties:Resources.Common_OK}" Height="25" VerticalAlignment="Center" Width="90" Margin="5"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>
User Control Code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SolidX.Base.MessageBoxes
{
/// <summary>
/// Interaction logic for OKMessageBox.xaml
/// </summary>
public partial class OKMessageBox : UserControl
{
public OKMessageBox(string Message)
{
InitializeComponent();
textMessage.Text= Message;
}
}
}
And in my user control, I am trying to have buttons that close the window (currently, setting Button.IsCancel = true; works, but I want this to be a reusable option - essentially making my own this.Close;).
I tried this (as suggested by a couple other StackOverflow answers) in the code-behind for my usercontrol but to no avail (parentWindow is always null):
var parentWindow = Window.GetWindow(this);
parentWindow.Close();
Try this in the UserControl:
xaml
<UserControl .... Loaded="UserControl_Loaded">
<!-- -->
</UserControl>
cs
public UserControl()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
var window = Window.GetWindow(this);
window.Close();
}
In case you are using ViewModel class for your Window, you can set the ViewModel class to your Window instance, and the ViewModel can store it's owner.
For example the ViewModel:
public class CloseConfirmViewModel
{
internal Window Owner { get; set; }
public ICommand CloseCommand { get; private set; } // ICommand to bind it to a Button
public CloseConfirmViewModel()
{
CloseCommand = new RelayCommand<object>(Close);
}
public void Close() // You can make it public in order to call it from codebehind
{
if (Owner == null)
return;
Owner.Close();
}
}
In order to get it work, you have to set the ViewModel class to your Window:
public partial class CloseConfirmWindow : Window
{
public CloseConfirmWindow(CloseConfirmViewModel model)
{
DataContext = model;
InitializeComponent();
model.Owner = this;
}
}
And you are creating a new instance this way:
var model = new CloseConfirmViewModel();
var closeWindow = new CloseConfirmWindow(model);
closeWindow.ShowDialog(); // Hopefully you've added a button which has the ICommand binded
You can bind the Window's CloseCommand to the UserControl's button this way, or if you are not using commands, call the Close() method when the UC's button is being clicked.
None of those approaches worked for me. Most just closed the parent window. I was using an XAML Popup from my main window. Something like.
<DockPanel/>
<Popup x:Name="puMyControl"/>
<Local:UserControl />
</Popup>
</DockPanel>
Elsewhere in my main window code behind, I added:
puMyControl.IsOpen = true;
Inside the UserControl I had a cancel button. Popup's do not have children, therefore I needed to reference the Popup's parent which was the DockPanel.
private void BtnClose_Click(object sender, RoutedEventArgs e)
{
Popup pu = Parent as Popup;
if ( pu != null )
{
DockPanel dp = pu.Parent as DockPanel;
if (dp != null)
{
dp.Children.Remove(pu);
}
}
}
The aha moment was, that I was removing the Popup, not the UserControl. 'this' would refer to the UserControl. As you can see above, I am removing the Popup from the DockPanel. That led to a simpler and more universal solution.
Popup pu = Parent as Popup;
if (pu != null)
{
pu.IsOpen = false;
}
To close modal dialog you should set DialogResult: DialogResult = false;

UserControl Data Binding retrieve value

with this simple code:
MainWindows:
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace itemcontrole_lesson
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public class TodoItem
{
public string Username { get; set; }
public int Completion { get; set; }
}
public partial class MainWindow : Window
{
List<TodoItem> items = new List<TodoItem>();
public MainWindow()
{
InitializeComponent();
items.Add(new TodoItem() { Username = "Eric", Completion = 45 });
items.Add(new TodoItem() { Username = "Maxwell", Completion = 80 });
items.Add(new TodoItem() { Username = "Sarah", Completion = 60 });
icTodoList.ItemsSource = items;
}
}
Mainwindows XAML:
<Window x:Class="itemcontrole_lesson.MainWindow"
xmlns:local="clr-namespace:itemcontrole_lesson"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl x:Name="icTodoList">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:UserControl1}">
<local:UserControl1 />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
then a simple UserControle:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace itemcontrole_lesson
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
//String CurentUserName = ????????
//Int Progress = ????????
}
private void Button_Click(object sender, RoutedEventArgs e)
{
///hows to remove the user from items in main windows??
}
}
}
UserControle XAML
<UserControl x:Class="itemcontrole_lesson.UserControl1"
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="54.181" Width="399.331">
<Grid Margin="0,0,-155,0">
<Label Content="{Binding Username}" HorizontalAlignment="Left" Margin="23,23,0,0" VerticalAlignment="Top"/>
<Button Content="Close this UC" HorizontalAlignment="Left" Margin="414,22,0,0" VerticalAlignment="Top" Width="119" Click="Button_Click"/>
<ProgressBar HorizontalAlignment="Left" Value="{Binding Completion}" Height="10" Margin="204,23,0,0" VerticalAlignment="Top" Width="154"/>
</Grid>
</UserControl>
Pressing F5 everything will bind ok if you test.
But! how I'm supposed to retrieve my variable value in my usercontrole code?
see where I put comment in UC.
1- I need at least to find a way to remove this control from UI and from Items list?.
2-I'd like access username in my control and set it into a var
any suggestion?
Solution 1:
Use Tag property of button like below:
<Button Content="Close this UC" HorizontalAlignment="Left" Margin="414,22,0,0"
VerticalAlignment="Top" Width="119" Click="Button_Click" Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
Event handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
List<object> list = (button.Tag as ItemsControl).ItemsSource.OfType<TodoItem>().ToList<object>();
list.Remove(button.DataContext);
(button.Tag as ItemsControl).ItemsSource = list;
}
Solution 2:
More elegant solution:
Create this Style in your MainWindow:
<Window.Resources>
<Style TargetType="Button">
<EventSetter Event="Click" Handler="Button_Click"/>
</Style>
</Window.Resources>
So now the Handler of any Button Click event is in the MainWindow.xaml.cs.
Then change handler definition like below:
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
items.Remove(button.DataContext as TodoItem);
icTodoList.ItemsSource = null;
icTodoList.ItemsSource = items;
}

Hide the TabControl header

What's the programmatic way (ie not using styles as in this question, but using code) to hide the TabControl header? I'll be glad for a snippet.
Actually, it's very straight forward to hide the tab strip. You just set each TabItems Visibility to Collapsed. You still see the tab content,...just not the tab header itself.
Style s = new Style();
s.Setters.Add(new Setter(UIElement.VisibilityProperty, Visibility.Collapsed));
tabControl.ItemContainerStyle = s;
Simple XAML Style
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</TabControl.ItemContainerStyle>
...
</TabControl>
Well, there are several ways to do this.
The ugliest way: Use VisualTreeHelper to find TabPanel (or any other Panel you use to host items), and set it's Visibility property to Visibility.Collapsed. Why ugly? It's easy to create few annoying bugs here or break this approach with 'harmless' style update if you was not careful enough...
I prefer using combination of Xaml and code behind. You bind either TabItem's visibility to view model property or TabPanel's visibility to view model property. In both cases you have to override style (either ItemContainer's style or whole TabControl's style). In both cases you have view model. Now, to toggle tab header's visibility, you just update a property in the view model. Here is an example with TabItems:
XAML
<Window x:Class="WpfApplication5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication5"
Title="Tab Settings"
Height="300"
Width="300">
<Window.Resources>
<local:TabControlViewModel x:Key="tabVM" />
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<TabControl DataContext="{StaticResource tabVM}">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Visibility"
Value="{Binding TabHeaderVisible, Converter={StaticResource booleanToVisibilityConverter}}" />
</Style>
</TabControl.ItemContainerStyle>
<TabItem Header="Tab 1">
<StackPanel>
<TextBlock Text="Content" />
<Button Content="Toggle Header"
Click="ToggleHeaderClick" />
</StackPanel>
</TabItem>
<TabItem Header="Tab 2 Header">
<TextBlock Text="Tab 2 Content" />
</TabItem>
</TabControl>
</Grid>
</Window>
C#
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication5
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void ToggleHeaderClick(object sender, RoutedEventArgs e)
{
var tabControlVM =
((FrameworkElement)sender).DataContext as TabControlViewModel;
if (tabControlVM != null)
{
tabControlVM.TabHeaderVisible = !tabControlVM.TabHeaderVisible;
}
}
}
public class TabControlViewModel : INotifyPropertyChanged
{
private bool _tabHeaderVisible = true;
public ICommand ToggleHeader
{
get; private set;
}
public bool TabHeaderVisible
{
get { return _tabHeaderVisible; }
set
{
_tabHeaderVisible = value;
OnPropertyChanged("TabHeaderVisible");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
var changed = PropertyChanged;
if (changed != null)
{
changed(this, new PropertyChangedEventArgs(name));
}
}
}
}
I've tried this in some code where I populate the tab items manually...
tabItemToAdd.Visibility = Visibility.Collapsed;
...but then I had a weird thing happen where the second time I'd clear out the tab control's items, create the tab item again and use this approach before adding it to the tab control, the entire tab item and its contents were gone, not just the tab header. So I've had success with the programmatic equivalent of this solution:
tabItemToAdd.Template = new ControlTemplate();
If you use C# and set the x:Name for the TabItem, you can also manipulate the Visibility like so:
tabItemName.Visibility = Visibility.Collapsed;
private void TabItemControl_MouseEnter(object sender, MouseEventArgs e)
{
if (this.TabItemControl.IsSelected == false)
{
this.TabItemControl.Opacity = 100;
}
}
private void TabItemControl_MouseLeave(object sender, MouseEventArgs e)
{
if (this.TabItemControl.IsSelected == false)
{
this.TabItemControl.Opacity = 0;
}
}
private void TabAllControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (this.TabItemControl.IsSelected == false)
{
this.TabItemControl.Opacity = 0;
}
}

Categories

Resources