This is my current Scenario: I have several UserControls inside different TabItems on a single TabControl in a WPF Window. Something Like:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="S.C.A.R" WindowState="Maximized">
<TabControl Name="MainTabControl">
<TabItem Name="TabOps">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Local:ServiceList Height="Auto" CanInsert="True" CanCollapse="True" Grid.ColumnSpan="3" x:Name="SL" RecordState="Edit"/>
<Local:ReservationList CanCollapse="True" Grid.Row="1" RecordState="Edit" x:Name="RL"/>
<Local:DriverList CanDelete="False" CanInsert="False" CanCollapse="True" Grid.Row="1" Grid.Column="2" RecordState="Edit" x:Name="DL"/>
<Local:CustomerForm CanDelete="False" CanInsert="False" Grid.Row="2" Grid.ColumnSpan="3" RecordState="View" x:Name="CL"/>
</Grid>
</TabItem>
<TabItemItem Name="TabCodes">
<Local:CustomerList x:Name="CustomerCRUD" RecordState="View"/>
</TabItem>
<Button Grid.Row="1" Content="TEST" Click="Button_Click"/>
</Grid>
</Border>
</Window>
Sorry for the indentation. For some reason I can't get the code properly indented here :(
What I need to do is to determine (preferably in the TabControl.Load Method, which of my different UserControls are currently visible. I need to do this in a dynamic way, I cannot hardcode the relationship between the TabItems and their children, something like:
if (TabControl.SelectedItem is XXXX)... is not possible here, because this is a Dynamic UI and I have no way to know which controls are there up front.
I've been digging a little bit and found out that the TabItem controls do not appear in the Visual tree of their "children". I only see a ContentPresenter, and then the TabControl itself. It looks like the tabItems do not "contain" their own content, so I could not, for example, do a FindAncestor to the Tab Items.
Another interesting fact is that the Loaded event of my usercontrols is being called on startup. Regardless of whether or not they're visible on screen.
An ideal scenario will be to find an event that is only fired on my Usercontrols when the TabItem they are under gets selected.
Appreciate any ideas. Thanks in advance
You should be able to leverage the VisualTreeHelper and consequentrly this answer on SO to provide the TabItem.Content returned object and look for the your specified type, UserControl in this instance.
NOTE:
For additional details please see the comments which transpired in the SO's question.
Related
I have a wpf tab control in which I would like to have two columns. One column would always show a graph control that will be used to display data depending on the tab selected - but it will always be the same graph control.
Unfortunately, due to the design of the graph control, it is not possible to have more than one graph control mainly because the performance is dismal. I have tried that and it does not work properly.
The other column would show items like combo boxes, radio buttons, etc., that are specific to the selected tab - an example is below
I have also had the tab control in the right-hand column, but the layout of the individual tabs is congested in the right-hand column making for a less than ideal user experience.
Right now, I have the tab control hosted in a grid that has two columns with the column span set to two. For the right-hand pane, I have various group boxes and I control the visibility of those group boxes with triggers using the IsSelected property of the corresponding tab item. This, however, is causing other problems that I have traced to the visibility of the problematic controls.
What I would like to do is modify the template of the control so that I can host all the present controls within the tab control so that the graph control always displays on the left, and that the content of the right-hand tab is controlled by the selected tab.
I figure that doing this will involve either the control template or another template for the tab control, however, I have been unable to find anything like this so far. Is there a way to do something like this and if so, is there a guide to doing so or some hints as to how I might accomplish this?
Thanks.
The way I would approach this requirement would be something like below. And I would apply a template/style to buttons so that they have a look of a TabHeader.
<Grid Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal">
<StackPanel.Resources>
<ControlTemplate x:Key="ButtonTemplate">
<!--Template style-->
</ControlTemplate>
</StackPanel.Resources>
<Button>Root bending</Button>
<Button>S-N curve bending</Button>
<Button>S-N curve contact</Button>
</StackPanel>
<Grid Grid.Row="1" Grid.Column="0">
<!--Your graph control goes here-->
</Grid>
<Grid Grid.Row="1" Grid.Column="1">
<!--Show/hide these based on buttons-->
<!--Control 1 with combo boxes, radio buttons, etc.-->
<!--Control 2 with combo boxes, radio buttons, etc.-->
<!--Control 3 with combo boxes, radio buttons, etc.-->
</Grid>
</Grid>
you can declare ChartControl as a Resource and use it in every Tab.
To confirm that ChartControl is the same, type something in TextBox and then select another Tab. Text stays the same. Initialization time shown in TextBlock stays the same.
<Window x:Class="XamlApp.Window6"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="TabsWindow"
Height="480" Width="640">
<Window.Resources>
<Grid x:Key="IamChartControl" Background="Khaki">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Source={x:Static system:DateTime.Now}}" Margin="5"/>
<TextBox Text="hello" Grid.Row="1" Margin="5"/>
</Grid>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding Source='12345'}">
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ContentControl Content="{StaticResource IamChartControl}"/>
<TextBlock Grid.Column="1" Text="{Binding}"/>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Thanks for the suggestions. Given there is a large amount of existing code and since the graph control is not entirely WPF MVVM compliant, the better answer in this case would be the answer posted by Ritesh. Putting the chart in a resource would have required more of a rewrite of the code than I presently have time to do.
However, I figured out the problem that I was seeing - which was some controls were not showing bold text when I thought that they should be. This was entirely my fault.
On each tab for each field, I had multiple different labels that were made visible depending on the result set that the user chooses. It has been a long time since I visited this code, and what I was doing was adding the bold fontweight because it makes the values stand out better.
Embarrassingly, I forgot that I had implemented it this way.
Instead of the multiple different labels approach, I am going use a single label for each field and set the appropriate content binding in a multidata trigger as that will make the somewhat cleaner. Its a pretty complicated app.
I was going to delete this, but others have asked a similar question, however, I think Ritesh's answer is different than the other cases.
I need to create a Wpf application with this type of navigation:
A main screen with a fixed top panel, and the rest of the window that acts as a container for other views. The container can be populated with 0 or 1 views - there's never a situation when 2 or more windows should be seen at the time in this container.
I've added this very childish drawing I made:
The top green panel is the navigation panel, and when pressing "A" button, I need viewA to be opened in the red part of the window.
Similarly, when pressing "B" button, I need viewB to be opened in the red part of the window instead of viewA.
My questions:
What objects should I use to represent viewA, viewB and the containing window? Are these Windows? Pages?
What is the best way of switching between viewA and viewB? I prefer avoiding loading both viewA and viewB (and any other view I'll have) into memory and just hide\show them..
Thanks.
You could put all the "Green" stuff in directly in your window and "Red" stuff (UserControl likely) inside a ContentControl and then bind the Content property of that to the red's stuff. So it'd be like this:
<Window x:Class="garbage.MainWindow"
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>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!--Green stuff-->
<Button Content="A" />
<Button Content="B" Grid.Column="1"/>
<ContentControl Grid.Row="1" Grid.ColumnSpan="2" Content="{Binding RedStuff}">
</ContentControl>
</Grid>
Disclosure:
I am new to WPF, about a week into it.
Problem:
I am trying to modify the behavior of a GridSplitter, to make it snap to interesting positions, to show a label (that follows the splitter) with current position, to have a context menu driven from said label, etc. I have prototyped all of this successfully on one gridsplitter in one simple test application, with a combination of XAML and some code behind.
Of note is that because the GridSplitter can't host content, I placed the label in the same grid cell as the splitter so that they move together.
So far so good....
Now I wish to replicate my work so that I can use my new GridSplitter functionality in place of the native control in many locations, and furthermore, I wish to have two variants, a horizontal and a vertical. Sounds like inheritance...create a subclass derived from GridSplitter and add in the additional functionality. But all of the reading I have done leaves me wondering how to go about this, and if this is even possible without starting over again and building my own GridSplitter from scratch?
Ideas welcome. Until then I will resume the fetal position.
Thanks
This answer might help you resolve your issue: How to make GridSplitter to "snap" into another element?
By subscribing to the GridSplitterDragCompleted event, you can insert your logic to snap to "interesting" positions.
You should
create a new control derived from GridSplitter.
subscribe to DragCompleted event to implement snapping functionality like DLeh mentioned.
add a few new properties for Label , ContextMenu etc.
supply a style for your new control.
This answers how to place content in the splitter
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Row 0" Background="Orange"/>
<!--<GridSplitter Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" Height="20" Background="Purple"/>-->
<GridSplitter Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Stretch">
<GridSplitter.Template>
<ControlTemplate TargetType="{x:Type GridSplitter}">
<TextBlock Text="TextBlock splitter" Background="Yellow" FontWeight="Bold"/>
</ControlTemplate>
</GridSplitter.Template>
</GridSplitter>
<Button Grid.Row="2" Content="Row 0" Background="Salmon"/>
</Grid>
I want to modify the original ListBox control that the each item to have a CheckBox, Labels and a Button control inside.
Is there any optimal method to make that? without making Custom Control from the very beginning?
Making custom control that inherits ListBox could be not a bad idea, but don't know how...
Thank you!
I tried WPF but it was too difficult at this time. Actually, designing the control via XAML was easy, but managing the list items(add/delete with texts, get event from the button in each item) wasn't.
Since the question is tagged [WPF] I'm going to provide a WPF answer:
The first thing any developer who faces WPF immediately tries to do is to use it as if it were winforms. This is a big mistake.
If you're working with WPF, you really need to leave behind the traditional aproach used in archaic technologies such as winforms, and understand and embrace The WPF Mentality.
in WPF, you don't "add/delete with texts, get event from the button in each item" or any of that, simply because UI is not Data.
Instead, you define a simple Data Model:
public class MyData
{
public string MyText1 {get;set;}
public string MyText2 {get;set;}
}
and then declaratively define Data Bindings in the UI to "show" this data to the UI as opposed to "reading" or "writing" data to/from the UI:
<Window x:Class="WpfApplication14.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication14"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="5" Background="LightCyan" BorderBrush="LightSkyBlue" BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Text 1:" HorizontalAlignment="Right"/>
<Label Grid.Row="1" Grid.Column="0" Content="Text 2:" HorizontalAlignment="Right"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding MyText1}"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding MyText2}"/>
<Button Grid.Row="2" Grid.ColumnSpan="2" Content="Button" HorizontalAlignment="Center"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
And finally, you define the DataContext of the Window or View to a relevant instance or collection of such data:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = Enumerable.Range(0,10)
.Select(x => new MyData()
{
MyText1 = "Text1 - " + x.ToString(),
MyText2 = "Text2 - " + x.ToString()
});
}
}
All this results in:
See? really simple and beautiful.
Forget winforms, WPF Rocks. Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
The best way to do this, short of using WPF, is to create a custom UserControl which represents each item that will go in the list. Then, add a FlowLayoutPanel to your form. Set the panel's AutoScroll property to True. Then set its FlowDirection property to TopToBottom. Then, dynamically create one of your custom controls for each item that you need to add to the list and call the panel's Controls.Add method to add them to the list.
I have the following simple code (see below) I copied from a book. But I have a couple of questions about the line <Grid TextBlock.FontSize="48">.
From what I gather, TextBlock.FontSize is an attached property but I initially thought that attached properties were meant to reference parent objects (i.e. when the Grid.Row attached property references the parent Grid element). But from how it is used here it may be that my understanding is incorrect? Is this an attached property and if so can it be used for child elements?
Second, TextBlock.FontSize is set on the grid. But, no where in the xaml do I use a TextBlock element (that I know of). I only used Buttons with Content defined. Yet if I change the TextBlock.FontSize to a different value the font size changes. Therefore, how is TextBlock.FontSize being used? Where is the TextBlock?
Thank you in advance.
<Window x:Class="UseAGrid.MainWindow"
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 TextBlock.FontSize="48">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="250" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.RowSpan="2"
Content="2 Rows" />
<GridSplitter Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Width="1"
Background="Green"
ResizeBehavior="PreviousAndNext"
ResizeDirection="Columns" />
<Button Grid.Column="2"
Grid.ColumnSpan="2"
Content="2 Columns" />
<Button Grid.Row="1"
Grid.Column="2"
Content="1,2" />
<Button Grid.Row="1"
Grid.Column="3"
Content="1,3" />
</Grid>
</Window>
TextBlock.FontSize is not an attached property, it's just a regular dependency property. The MSDN documentation is pretty good at listing attached properties for a control and FontSize is not one of them (it doesn't even have any).
It is however an inheritable property. Look at the dependency property information for it and you'll see that it inherits its value. What this allows us to do is set the value of the property in an ancestor and all descendant controls that relies on this property will inherit the same value as long as they don't explicitly set the value to something else.
The controls it applies to doesn't have to be explicitly instantiated by you, it also applies to styles, templates, content presenters, etc. So in your case, the content of your buttons is text so the TextBlocks used to display that text will also inherit the font size.
See Property Value Inheritance for more information.