WPF find clicked button closest parent (TabItem) and close it - c#

I am wondering whether there is a better simpler way rewrite the following code find the closest parent which is a TabItem and remove it from the TabControl.
I have a TabControl where I add new TabItems dynamically. I assign a HeaderTemplate to each tab which looks like this;
<DataTemplate x:Key="AttorneyTabHeader">
<StackPanel Orientation="Horizontal">
<TextBlock Text="THE title" Margin="2,0,0,0" FontSize="16" VerticalAlignment="Center" />
<Button Width="Auto" UseLayoutRounding="False" BorderBrush="Transparent" Background="Transparent" Click="CloseAttorneysTabButtonClick">
<Image Source="/images/close-cross-thin-circular-button/close-cross-thin-circular-button16.png" Height="16"></Image>
</Button>
</StackPanel>
</DataTemplate>
The header has a close button and I would like to close the TabItem whenever the button is clicked. My click handler looks like this;
public void CloseAttorneysTabButtonClick(object sender, RoutedEventArgs e)
{
TabItem this_tab = (TabItem)((Button)sender).Parent.GetParentObject().GetParentObject().GetParentObject().GetParentObject().GetParentObject().GetParentObject();
AttorneysTabControl.Items.Remove(this_tab);
}
I am wondering whether there is a better way to rewrite this because now I am depending on getting the parent over and over again suppose I change the button and forget changing the handler.

There's probably a few ways you can handle it, but the simplest is likely to bind to the TabItem in the Tag property for the Button so that you can use it in your event handler.
<DataTemplate x:Key="TabHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="The Title" Margin="2 0 0 0" FontSize="16" VerticalAlignment="Center" />
<Button Width="Auto" UseLayoutRounding="False"
BorderBrush="Transparent" Background="Transparent"
Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabItem}}, Mode=OneWay}"
Click="Button_Click">
<Image Source="images/close.png" Height="16" />
</Button>
</StackPanel>
</DataTemplate>
Now your event handler can be relatively simple, and doesn't need to know as much as it does in your example.
void Button_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is TabItem item) {
var tabControl = (TabControl)item.Parent;
tabControl.Items.Remove(item);
}
}

Related

What is approach to overlap one element above another in XAML when they get/lost focus?

I have two DockPanel elements:
<DockPanel Margin="10">
<TextBlock Text="+ Add new note" />
</DockPanel>
<DockPanel Margin="10">
<ComboBox>
<ComboBoxItem>One</ComboBoxItem>
<ComboBoxItem>Two</ComboBoxItem>
</ComboBox>
<Button Content="Add Note" DockPanel.Dock="Right" Margin="0" Padding="10,0" />
<TextBox Margin="10,0" />
</DockPanel>
I want to get follow result: once I click on first DockPanel, it must be hidden and second one is showed instead. After I click on button Add Note, second DockPanel must be hidden and first one must be appeared again.
I wonder an approach how to make it but don't know where to begin.
I'm appreciate for any suggestions.
A rough solution (no MVVM base) might be the following:
MainWindow.xaml
<Grid>
<DockPanel Margin="10" x:Name="EditDP" Visibility="Collapsed">
<ComboBox>
<ComboBoxItem>One</ComboBoxItem>
<ComboBoxItem>Two</ComboBoxItem>
</ComboBox>
<Button Content="Add Note" DockPanel.Dock="Right" Margin="0" Padding="10,0" Click="ButtonBase_OnClick"/>
<TextBox Margin="10,0" />
</DockPanel>
<DockPanel x:Name="MainDP" Margin="10" MouseLeftButtonDown="UIElement_OnMouseLeftButtonDown">
<TextBlock Text="+ Add new note" />
</DockPanel>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void UIElement_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.MainDP.Visibility = Visibility.Collapsed;
this.EditDP.Visibility = Visibility.Visible;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
this.MainDP.Visibility = Visibility.Visible;
this.EditDP.Visibility = Visibility.Collapsed;
}
}
Bind the Visibility property of both the dockpanels to the same boolean and use InverseBooleanToVisibilityConverter / BooleanToVisibilityConverter on each of them.
This way when you flip the boolean using either the click event on the first Dock panel or the button click on the second panel, the two panels will alternatively show up

How can i duplicate and place new stackpanels on buttonpress at runtime?

I have a stack panel in XAML which currently has a column of 7 textboxes. I have a button on my form which i would like to duplicate the current stackpanel and place a new instance of it next to the existing one every time it is clicked.
Is this possible?
My Try:
var mynewstackpanel = new StackPanel();
var entry1 = new TextBox();
var entry2 = new TextBox();
entry1.Name = "newbox1";
entry1.Text= "newboxtext1";
entry2.Name = "newbox2";
entry2.Text = "newboxtext2";
mynewstackpanel.Children.Add(entry1);
mynewstackpanel.Children.Add(entry2);
You want to create the controls dynamically actually. So WPF has got a great support for that.
You can do this using ItemsControl and changing its ItemTemplate to have whatever controls you want to generate. Precisely this is what you want to do:
Take an ItemsControl in the xaml and bind the ItemsSource to an ObservableCollection in the ViewModel.
Change ItemsControl's ItemTemplate to have your StackPanel you are talking about.
Now in button click command, just keep adding data to your ObservableCollection.
This is a rough idea of how you can achieve it. You will have to code yourself.
P.S. Please mark as answered if you feel I answered it.
You can use a ListBox and DataTemplate for this case. You Add items to the ListBox Horizontally using ItemsPanel and define a ItemsPanelTemplate. Refer below code.
<StackPanel>
<ListBox x:Name="items">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel>
<TextBox Width="100" />
<TextBox Width="100" />
<TextBox Width="100" />
<TextBox Width="100" />
<TextBox Width="100" />
<TextBox Width="100" />
<TextBox Width="100" />
</StackPanel>
<StackPanel>
<TextBox Width="35" />
<TextBox Width="35" />
<TextBox Width="35" />
<TextBox Width="35" />
<TextBox Width="35" />
<TextBox Width="35" />
<TextBox Width="35" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<Button Content="Add Content" Click="Button_Click"></Button>
</StackPanel>
public partial class MainWindow : Window
{
ObservableCollection<string> lst;
public MainWindow()
{
InitializeComponent();
lst = new ObservableCollection<string>();
items.ItemsSource = lst;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
lst.Add("");
}
}

How to delete a Template on a Button click

i have ControlTemplate, which is used as a resource inside a ListBox, i am trying to delete the template i.e remove it totally from ListBox on a Button click
here is the code for template
<ControlTemplate x:Key="tasktemplate1">
<Canvas Height="50" Width="850">
<Label Content="{Binding XPath=task[1]/name}" Height="30" Width="170" Canvas.Top="10" Canvas.Left="150" Background="LightGray">
</Label>
<TextBox Height="30" Width="120" Canvas.Top="10" Canvas.Left="370" Background="AliceBlue"></TextBox>
<Label Canvas.Left="500" Canvas.Top="10">$</Label>
<Button Click="deletebuttonclick" Canvas.Top="12" Height="10" Width="30" Canvas.Left="600" ></Button>
</Canvas>
</ControlTemplate>
here is the code for ListBox
<TabItem>
<Canvas Height="700" Width="850">
<ListBox x:Name="listBox" Height="700" Width="850">
<ListBoxItem DataContext="{Binding Source={StaticResource TaskList}}" Template="{StaticResource tasktemplate1}"/>
</ListBox>
</Canvas>
</TabItem>
and the code behind is for the Button click
private void deletebuttonclick(object sender,RoutedEventArgs e)
{
var r=listBox.FindResource("tasktemplate1");
listBox.Items.Remove(r);
}
where am i going wrong,help needed,thanx.
ControlTemplate is just visual representation of control that how it will look like.
So, you need to remove the item (ListBoxItem) from the Items collection and not Template. Since templated control is removed, template automatically will be removed.
private void deletebuttonclick(object sender,RoutedEventArgs e)
{
listBox.Items.RemoveAt(0);
// listBox.Items.Clear(); OR in case want to clear all listBoxItems, use Clear
}

ScrollToVerticalOffset() doesn't work?

I'm using ScrollView in WPF, and my app allow user click on a button and then it will auto scroll the scrollview, I use ScrollToVerticalOffset() in button click event, but the scrollview not changed anything.
I searched about this issue on internet, but so far I can't not solve it yet.
And one more question: ScrollToVerticalOffset() take a double as parameter, it may will scroll to the special pixels, there any way to scroll to n items (not pixel)?
Here is my code
<ScrollViewer x:Name="scrollViewerChannelBtns" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Center"
Background="Transparent" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden"
CanContentScroll="True" ScrollChanged="ScrollViewerChannelBtns_ScrollChanged">
<StackPanel x:Name="channelBtns" Orientation="Vertical">
<ItemsControl x:Name="channelBtnItems" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton x:Name="tgbChannelName" Width="{Binding Path=ChannelNameBtnWidth}" Height="{Binding Path=ChannelNameBtnHeight}" HorizontalAlignment="Left" VerticalAlignment="Center" IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" Content="{Binding Path=ChannelName}" Tag="{Binding Path=Index}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
C# Code
//The button click event handled
private void BtnScrollDownClicked(object sender, RoutedEventArgs e)
{ scrollViewerChannelBtns.ScrollToVerticalOffset(scrollViewerChannelBtns.VerticalOffset + 50);
}
private void BtnScrollUpClicked(object sender, RoutedEventArgs e)
{ scrollViewerChannelBtns.ScrollToVerticalOffset(scrollViewerChannelBtns.VerticalOffset - 50);
}
Many thanks,
T&T
Mine is started work after;
ScrollViewer.UpdateLayout();
ScrollViewer.ScrollToVerticalOffset(outPoint.Y);
For me, this example works:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
<Grid>
<ScrollViewer x:Name="scrollViewerChannelBtns" HorizontalAlignment="Center" Height="100" CanContentScroll="False" VerticalAlignment="Center"
Background="Transparent" VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="channelBtns" Orientation="Vertical">
<ItemsControl x:Name="channelBtnItems">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton x:Name="tgbChannelName" Width="40" Height="20" HorizontalAlignment="Left" VerticalAlignment="Center" IsChecked="{x:Null}" Content="Test" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<sys:String>Item 1</sys:String>
<sys:String>Item 2</sys:String>
<sys:String>Item 3</sys:String>
<sys:String>Item 4</sys:String>
<sys:String>Item 5</sys:String>
<sys:String>Item 6</sys:String>
<sys:String>Item 7</sys:String>
<sys:String>Item 8</sys:String>
<sys:String>Item 9</sys:String>
<sys:String>Item 10</sys:String>
</ItemsControl>
</StackPanel>
</ScrollViewer>
<Button Name="Up" Width="50" Height="30" VerticalAlignment="Top" Margin="110,0,0,0" Content="Up" Click="Up_Click" />
<Button Name="Down" Width="50" Height="30" VerticalAlignment="Top" Margin="210,0,0,0" Content="Down" Click="Down_Click" />
</Grid>
In the example I set the height for the ScrollViewer and CanContentScroll set false. Quote from answer why setting ScrollViewer.CanContentScroll to false disable virtualization:
ScrollViewer currently allows two scrolling modes: smooth pixel-by-pixel scrolling (CanContentScroll = false) or discrete item-by-item scrolling (CanContentScroll = true). Currently WPF supports UI virtualization only when scrolling by item. Pixel-based scrolling is also called “physical scrolling” and item-based scrolling is also called “logical scrolling”.
Virtualization requires item-based scrolling so it can keep track of logical units (items) currently in view... Setting the ScrollViewer to pixel-based scrolling their is no more concept of logic units but only pixels!
Code behind
private void Up_Click(object sender, RoutedEventArgs e)
{
scrollViewerChannelBtns.ScrollToVerticalOffset(scrollViewerChannelBtns.VerticalOffset - 50);
}
private void Down_Click(object sender, RoutedEventArgs e)
{
scrollViewerChannelBtns.ScrollToVerticalOffset(scrollViewerChannelBtns.VerticalOffset + 50);
}
Scrolling elements not supported by default, so you have to look at these links:
Consolidated Scrolling - "Pixel by Pixel" + "Item by Item"
ScrollViewer's Viewport Height VS Actual Height

Identifying a button in a templated ListBox

I have a ListBox that has a custom DataTemplate assigned to it, so that it can correctly display it's content - a custom "Accessory" (which consists of three string properties) object per row. Additionally, there is a button on each row. That button should trigger an event that adds the selected accessory to a MemoryList. Here is the DataTemplate:
<DataTemplate x:Key="AccessoryListBoxTemplate">
<StackPanel>
<!--Truncated-->
<TextBlock FontFamily="Avenir Next LT Pro" VerticalAlignment="Center" FontSize="14" Text="{Binding Path=AgilityHeader}" Margin="3,0,0,0" Grid.Column="0" />
<TextBlock FontFamily="Avenir Next LT Pro" VerticalAlignment="Center" FontSize="14" Text="{Binding Path=ItemNumber}" Grid.Column="1" />
<TextBlock FontFamily="Avenir Next LT Pro" HorizontalAlignment="Right" VerticalAlignment="Center" Text="{Binding Path=Price}" FontSize="14" Grid.Column="2" />
<Button x:Name="ButtonAccessoryAddToMemoryList" VerticalAlignment="Center" Click="buttonAccessoryAddToMemoryList_Click" HorizontalAlignment="Right" FontSize="14" Width="80" Grid.Column="3" Margin="0,5,0,5">Minneslista</Button>
</Grid>
</StackPanel>
</StackPanel>
</DataTemplate>
And here is the ListBox:
<ListBox Grid.ColumnSpan="3" Grid.Row="1" BorderThickness="0" x:Name="ListBoxAccessories" ItemTemplate="{StaticResource AccessoryListBoxTemplate}" HorizontalContentAlignment="Stretch" ItemsSource="{Binding}" SelectedIndex="-1" IsEnabled="True" />
The problem I'm having is this - I cannot reliably identify on which row ButtonAccessoryAddToMemoryList is clicked, since the row that the button is on is not set as the SelectedItem for the ListBox if the user doesn't first select the row and then push the button - and honestly, who does that? :)
How should I go about identifying which button was pressed? Any help would be greatly appreciated. Thanks!
[EDIT] Thanks to Chadwick for that answer. Works perfectly. [/EDIT]
If what you're really after is to know which Accessory object was clicked on you can set the Tag property of the button:
<Button x:Name="ButtonAccessoryAddToMemoryList" Tag="{Binding}" Click="buttonAccessoryAddToMemoryList_Click" ... >Minneslista</Button>
and then cast out the object in the click handler:
private void ButtonAccessoryAddToMemoryList(object sender, RoutedEventArgs e)
{
Button b = e.Source as Button;
Accessory a = b.Tag as Accessory;
Try out M-V-VM pattern and a command binding. If the datatemplate were bound to its own object and the command were activated, then you would already know the record being clicked.
In the XAML, you could give the button a _Loaded event handler. Then, in the code behind, set the _Click event. Perhaps make an array of delegates so that the clicks will call different handlers.

Categories

Resources