Alternate ways to display forms inside a Grid row in WPF - c#

The WPF application I've created contains a Grid with 3 rows. I have a set of buttons in the 3rd row. On Clicking the Buttons, the forms are displayed in the 2nd row of the Grid. However What I've done is quite messy because I've created all the form contents in the same window and have set the visibility to Hidden.
sample code:
<Grid Name="panel1" Grid.Row="1" Visibility="Hidden">
//contains a lot of textblocks,buttons and images
</Grid>
<Grid Name="panel2" Grid.Row="1" Visibility="Hidden">
//contains a lot of textblocks,buttons and images
</Grid>
<Grid Name="panel3" Grid.Row="1" Visibility="Hidden">
//contains a lot of textblocks,buttons and images
</Grid>
My xaml Code looks like this:
private void Image_MouseLeftButtonDown_1(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
panel1.Visibility = System.Windows.Visibility.Hidden;
panel2.Visibility = System.Windows.Visibility.Visible;
panel3.Visibility = System.Windows.Visibility.Hidden;
}
This approach looks really messy as I've included all the code in the Main Xaml file.
Need some alternate ways to do this. Kindly help.

Create a method like this
private void VisibilityManager(Grid grd)
{
new List<Grid>(){ panel1, panel2, panel3}
.ForEach(x => x.Visibility = Visibility.Hidden);
grd.Visibility = Visibility.Visible;
}
and call it like this
VisibilityManager(panel1);
for making panel1 visible and all other hidden.
In future if you have to increase your grids just make changes inside this method.

You could style a tabcontrol and switch the tab, you could use a contentcontrol and create a binding to the content you want to show. You could also use a databinding (depends on what you want to display) and use a template selector... there are many ways to solve that problem. Just tell a bit more what your contents look like

Related

TabControl SelectedContent not returning the current selected TabItem content

Currently I have a TabControl with several TabItems. Each TabItem has a DataGrid inside. I wanted to format these DataGrids (cell colors, column widths, etc) all at once but I found I can't because all the DataGrids from the hidden tabs would return null properties. In this case, I tried to make a work around where I would select programmatically (or manually with the mouse) the tabs before formatting the DataGrid. But now I'm up against a "strange" behavior:
private void LeftTabs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Console.WriteLine(LeftTabs.SelectedIndex);
var currentDataGrid = (DataGrid)LeftTabs.SelectedContent;
Console.WriteLine(currentDataGrid.Name);
}
The selected index returns the correct tab index, but the content it's not updated.
Let's say Tab 1 is selected and then I click on Tab 2. It returns me the Tab 2 index and the Tab 1 DataGrid name.
This behavior prevents me from editing the select tab's DataGrid because even if I try to access it directly by it's object, all the properties return null.
This is the TabControl, item and DataGrids XAML code:
<TabControl Name="LeftTabs" Margin="0,0,0,0" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" SelectionChanged="LeftTabs_SelectionChanged">
<TabItem>
<TabItem.Header>Conditions</TabItem.Header>
<DataGrid x:Name="DataGrid_Conditions" SelectedCellsChanged="DataGrid_Conditions_SelectedCellsChanged" ColumnWidth="80" ItemsSource="{Binding}" HorizontalAlignment="Stretch" VerticalAlignment="Top" SelectionChanged="ConditionsSelected" />
</TabItem>
<TabItem>
<TabItem.Header>Signals</TabItem.Header>
<DataGrid x:Name="DataGrid_Signals" ColumnWidth="80" ItemsSource="{Binding}" HorizontalAlignment="Stretch" VerticalAlignment="Top" SelectionChanged="SignalsSelected" />
</TabItem>
</TabControl>
EDIT:
To be more clear I'll minimize the scenario.
TabItem1 - Has DataGrid_Conditions inside;
TabItem2 - Has DataGrid_Signals inside.
Here is another code that I try to run when I manually or programmatically select a tab:
DataGridRow Row = (DataGridRow)DataGrid_Signals.ItemContainerGenerator.ContainerFromIndex(ID);
What happens is, If I click on tab2 this code doesn't for tab2's Grid. Instead it works for the previous tab(1) Grid.
The LeftTabs.SelectedContent is the only property not being updated.
I can't seem to reproduce your issue. "DataGrid_Signals" should be printed out when you selecte the second tab. You may also get a reference to the currently selected TabItem from the SelectionChangedEventArgs:
private void LeftTabs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Debug.WriteLine(LeftTabs.SelectedIndex);
TabItem tabItem = e.AddedItems[0] as TabItem;
var currentDataGrid = (DataGrid)tabItem.Content;
Debug.WriteLine(currentDataGrid.Name);
}
In this example despite it indeed printing "DataGrid_Signals" when I try to get anything from the grid's object, it just comes null as it was in a hidden.
This is because it has not yet been loaded. You can force it to render by measure and arrange it:
private void LeftTabs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (IsLoaded)
{
TabItem tabItem = e.AddedItems[0] as TabItem;
var currentDataGrid = (DataGrid)tabItem.Content;
currentDataGrid.Measure(new Size(currentDataGrid.ActualWidth, currentDataGrid.ActualHeight));
currentDataGrid.Arrange(new Rect(0, 0, currentDataGrid.ActualWidth, currentDataGrid.ActualHeight));
//...
}
}
This is a bug, fixed in .NET 4.7.1 but quirked so that apps targeting 4.7 or below still get the old behavior. See the breaking change announcement for details.

how to delete a selected text box that was created at run time

I have a WPF application that has two buttons- Add and Remove. The add button adds text boxes in a specific grid in the gui at the run time programmatically and text box names will be assigned at runtime too. I want the delete button to delete the selected text box that was generated at the run time from the gui. I am not aware of a way to delete the text box unless I know the text box name and I am not sure which way to go regarding this. I would appreciate even a little guidance. I am very new to WPF and I am sure I should be missing some obvious.
Thanks in advance.
If you're using MVVM, (which you should be in WPF), you can do this:
In the ViewModel, expose a public ObservableCollection<T> that would contain the business objects (e.g. a User) that you need to show TextBoxes for.
In the UI, add an ItemsControl and bind it to your ObservableCollection.
Define a DataTemplate that translates the business objects into TextBoxes and binds TextBox properties to business objects members.
Implement Add and Remove RelayCommands in the ViewModel.
Bind your Add and Remove buttons with these commands.
This will save you from the hectic of walking the visual tree and finding appropriate textboxes etc.
Here is a basic demo to add and remove elements in/from Grid :
XAML:
<Window x:Class="TabControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TabControl"
Title="MainWindow" Height="300" Width="300"
xmlns:Interact="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"
>
<ScrollViewer VerticalScrollBarVisibility="Visible">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button Content="Add New Box" Click="Button_Click" />
<Button Content="Remove Selected Box" PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown" />
</StackPanel>
<Grid x:Name="mygrid">
</Grid>
</StackPanel>
</ScrollViewer>
Events:
private void Button_Click(object sender, RoutedEventArgs e)
{
var textBox=new TextBox();
mygrid.RowDefinitions.Add(new RowDefinition());
textBox.Name = "textBox" + mygrid.RowDefinitions.Count;
textBox.SetValue(Grid.RowProperty, mygrid.RowDefinitions.Count);
mygrid.Children.Add(textBox);
}
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var focusedElement = Keyboard.FocusedElement;
if (focusedElement is TextBox)
{
mygrid.Children.Remove(focusedElement as UIElement);
}
}
Output
Above is very basic WPF approach you can take, However i very much recommend you to look into MVVM pattern to easy to logic separation and flexibility (like #dotNEt suggested in his answer).

Working with elements inside a control that is bound to a collection

I have an ObservableCollection<string> that is bound to an ItemsControl whose template is just a Button. The content of this button are 2 TextBlock. I'm trying to use the PreviewMouseRightButtonUp event of the button to toggle the visibility of one of the textblocks, but without being able to use xaml names for elements in the template I'm hitting a wall. Is there a way of getting to the button's content elements via sender in that preview event, or some other way of doing this? This is related to a previous question I had that didn't quite get a usable answer (probably due to my explanation, hence this simplified example). It seems to me that what should happen is I should make a control based off button that adds a property for this toggle, but that is basically what I thought I had in the previous question that wasn't working. I feel like a property and trigger is what most would say is the right way to go?
xaml:
<ItemsControl x:Name="iC" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button PreviewMouseRightButtonUp="Button_PreviewMouseRightButtonUp">
<DockPanel>
<TextBlock Text="normal" DockPanel.Dock="Top"/>
<TextBlock Text="{Binding}" DockPanel.Dock="Top" Visibility="Collapsed"/>
</DockPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
code behind:
ObservableCollection<string> x = new ObservableCollection<string>();
public MainWindow()
{
x.Add("1");
x.Add("2");
InitializeComponent();
iC.ItemsSource = x;
}
If you name the hidden text block "secondTextBlock", then this should work:
private void Button_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
DockPanel dockPanel = (DockPanel)((Button)sender).Content;
TextBlock text = (TextBlock)LogicalTreeHelper.FindLogicalNode(dockPanel, "secondTextBlock");
if (text != null)
{
text.Visibility = Visibility.Visible;
}
}
Regarding your comment below: yes, multiple instances of "secondTextBlock" will be created. See the Snoop screenshot below. But these multiple instances are OK; they do not have any negative impact.

Adding Animation to panels when they are made visible in WPF

I am creating an application in WPF and the window has one main grid with 3 rows. There are 3 buttons in the 3rd row and on the click of each button, a panel is displayed in the 2nd grid row. I achieved this by setting the visibility option of the panels. However, now I would like to add an effect/animation as the panels become visible. I don't know where to start, so kindly help.
My xaml code is similar to this
<Window>
<Grid>
<!-- 3row definitions -->
<Grid Grid.Row="0"> </Grid>
<Grid Name="panel1" Grid.row="1" Visibility="Hidden"></Grid>
<Grid Name="panel2" Grid.row="1" Visibility="Hidden"></Grid>
<Grid Name="panel3" Grid.row="1" Visibility="Hidden"></Grid>
<Grid Grid.Row="2"></Grid>
</Grid>
</Windows>
Xaml.cs code to change the visibility is similar to this
private void Image_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
panel1.Visibility = System.Windows.Visibility.Visible;
panel2.Visibility = System.Windows.Visibility.Hidden;
panel3.Visibility = System.Windows.Visibility.Hidden;
}
this can be done using expression studio, in expression blend open your wpf projects, there you can add animations to your wpf controls, you also need to start and stop animation when your application launches,
http://www.youtube.com/watch?v=JpGvl1TayAQ
here is a video tutorial, you can get more tutorials by googling it,

Toolbar Overlay over WindowsFormsHost

I have a SWF object embedded in a WindowsFormsHost Control inside a WPF window.
I'd like to add a toolbar over the swf movie.
The problem with the snippet of code I have below, is that when the new child is added to the host control (or the movie is loaded, I haven't figured out which yet), the toolbar is effectively invisible. It seems like the z-index of the swf is for some reason set to the top.
Here is what it looks like:
XAML:
<Grid Name="Player">
<WindowsFormsHost Name="host" Panel.ZIndex="0" />
<Grid Name="toolbar" Panel.ZIndex="1" Height="50"
VerticalAlignment="Bottom">
[play, pause, seek columns go here]
</Grid>
</Grid>
C#:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
flash = new AxShockwaveFlashObjects.AxShockwaveFlash();
host.Child = flash;
flash.LoadMovie(0, [movie]); // Movie plays, but no toolbar :(
}
Any insight on this issue would be much appreciated.
Update: Since no suitable answer was posted, I've placed my own solution below. I realize this is more of a hack than a solution so I'm open to other suggestions.
Here is my hackaround the WindowsFormsHost Z-index issue.
The idea is to place whatever you need to be overlayed nested inside a Popup. Then to update that popup's position as per this answer whenever the window is resized/moved.
Note: You'll probably also want to handle events when the window becomes activated/deactivated, so the pop disappears when the window goes out of focus (or behind another window).
XAML:
<Window [stuff]
LocationChanged="Window_LocationChanged"
SizeChanged="Window_SizeChanged" >
<Grid Name="Player">
[same code as before]
<Popup Name="toolbar_popup" IsOpen="True" PlacementTarget="{Binding ElementName=host}">
[toolbar grid goes here]
</Popup>
</Grid>
</Window>
C#:
private void resetPopup()
{
// Update position
// https://stackoverflow.com/a/2466030/865883
var offset = toolbar_popup.HorizontalOffset;
toolbar_popup.HorizontalOffset = offset + 1;
toolbar_popup.HorizontalOffset = offset;
// Resizing
toolbar_popup.Width = Player.ActualWidth;
toolbar_popup.PlacementRectangle = new Rect(0, host.ActualHeight, 0, 0);
toolbar_popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Top;
}
private void Window_LocationChanged(object sender, EventArgs e)
{ resetPopup(); }
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{ resetPopup(); }
Another solution I've discovered is to use Windows Forms' ElementHost control. Since I'm using a Windows Form inside a WPF window anyway, why not just use an entire Windows Form and save myself Z-Issue headaches.
The ElementHost control is really useful, because I can still use my toolbar UserControl, and embed it inside the Windows Form. I've discovered that adding a child can be finicky with Windows Forms, so here's a snippet describing the solution:
First, toss in the ActiveX object, then an ElementHost Control, using the designer.
Form1.Designer.cs:
private AxShockwaveFlashObjects.AxShockwaveFlash flash;
private System.Windows.Forms.Integration.ElementHost elementHost1;
Form1.cs
public Form1(string source)
{
InitializeComponent();
toolbar = new UserControl1();
this.elementHost1.Child = this.toolbar;
this.flash.LoadMovie(0, source);
}
Note that the child was not set in the designer. I found that for more complex UserControls the designer will complain (though nothing happens at runtime).
This solution is, of course, still not entirely ideal, but it provides the best of both worlds: I can still code my UserControls in XAML, but now I don't have to worry about Z-indexing issues.

Categories