Programmatically switching tabs in a Tab Control - c#

I am wondering how to switch to a different tab within a tab control.
I have a main window that has a tab control associated with it and it directs to different pages. I want to switch to a tab from an event triggered within a different tab. When I try to use TabControl.SelectedIndex I get the error "An object reference is required to access non-static, method or property 'MainWindow.tabControl'
Here is my code declaring the TabControl from the MainWindow and trying to switch to it from a different tab.
<TabControl Name="tabControl" Margin="0,117,0,0" SelectionChanged="tabControl_SelectionChanged" Background="{x:Null}" BorderBrush="Black">
<TabItem x:Name="tabMO" Header="MO" IsTabStop="False">
<Viewbox x:Name="viewMO" Margin="0,0,0,0" Stretch="Fill" StretchDirection="Both">
<local:ManufacturingOrder x:Name="mo" Height="644" Width="1322"/>
</Viewbox>
</TabItem>
<TabItem x:Name="tabOptimize" Header="Optimize" IsTabStop="False">
<Viewbox x:Name="viewOptimize" Margin="0,0,0,0" Stretch="Fill" StretchDirection="Both">
<local:EngineeringOptimization x:Name="Optimize" Height="644" Width="1600"/>
</Viewbox>
</TabItem>
</TabControl>
private void dataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var cellInfo = dataGrid.SelectedCells[0];
var content = (cellInfo.Column.GetCellContent(cellInfo.Item) as TextBlock).Text;
var r = new Regex("[M][0-9]{6}");
if (r.IsMatch(content.ToString()))
{
MainWindow.tabControl.SelectedIndex = 4;
}
}
I have tried switching this to a private static void and received the same error.
I have also tried the following code, creating an instance of MainWindow, and there is no errors but when I run the code the selected tab doesn't change on the screen. But if I use a MessageBox to view the Selected Index, than I see my changed tab Index.
private void dataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var cellInfo = dataGrid.SelectedCells[0];
var content = (cellInfo.Column.GetCellContent(cellInfo.Item) as TextBlock).Text;
var r = new Regex("[M][0-9]{6}");
if (r.IsMatch(content.ToString()))
{
MainWindow frm = new MainWindow();
frm.tabControl.SelectedIndex = 4;
}
}

It looks like your main problem is that you do not have easy access to your MainWindow and all of its children from within your ManufacturingOrder or EngineeringOptimization UserControls. Which is normal. There are a few ways around this. A simple one, which violates some MVVM principles, (but you're doing that anyway, so I don't think you'll mind) is to retrieve the instance of your MainWindow object:
//Loop through each open window in your current application.
foreach (var Window in App.Current.Windows)
{
//Check if it is the same type as your MainWindow
if (Window.GetType() == typeof(MainWindow))
{
MainWindow mWnd = (MainWindow)Window;
mWnd.tabControl.SelectedIndex = 4;
}
}
Once you retrieve the running instance of your MainWindow, then you have access to all its members. This has been tested as well as possible without access to your specific custom UserControls and instances. But it's a pretty standard problem and solution.
You were on the right track with your last bit of code in your question, but you were creating a 'new' instance of your MainWindow. You have to retrieve the current running instance, not a new instance.

Related

WPF - How to persist current content when setting the content property

I'm facing a problem where by setting the content property of my window I obviously remove pre-existing content. On all windows I have a dockpanel that I use to pop up help contextual help to the user but this is lost when I set the content property of the window. Therefore I will only see the content for the control I've added and pressing F1 does nothing as the dockpanel does not exist. I don't want to add this dockpanel to every control as it's poor code-reuse so what can I do to keep the dockpanel on the window and add content without overwriting original content of the window?
This is the code where I set the content of the window.
private void btnHelp_Click(object sender, RibbonControlEventArgs e)
{
System.Windows.Window window = new ResizeableWindow()
{
Title = "Help",
Content = new Controls.Help(),
ResizeMode = ResizeMode.NoResize
};
window.ShowDialog();
}
This is code for my Help control it's just a document viewer to read an xps document, this is used by the dockpanel.
public partial class Help : UserControl
{
public Help()
{
InitializeComponent();
string appPath = "path";
XpsDocument doc = new XpsDocument(appPath, FileAccess.Read);
var docx = doc.GetFixedDocumentSequence();
HelpDocViewer.Document = docx;
}
}
This is the xaml of my ResizableWindow containing the Dockpanel
<Window x:Class="Controls.ResizeableWindow"
KeyDown="HelpKeyListen">
<Grid>
<DockPanel x:Name="HelpPanel">
</DockPanel>
</Grid>
</Window>
Here is the code for the resizeable window
public ResizeableWindow()
{
InitializeComponent();
}
private void HelpKeyListen(object sender, KeyEventArgs e)
{
if (e.Key == Key.F1)
{
var HelpControl = new Help();
DockPanel.SetDock(HelpControl, Dock.Right);
HelpPanel.Children.Insert(0, HelpControl);
}
}
Use Placeholders inside the DockPanel instead of replacing the window content:
<DockPanel x:Name="HelpPanel">
<ContentControl x:Name="HelpContent" DockPanel.Dock="Right"/>
<ContentControl x:Name="MainContent"/>
</DockPanel>
Then assign the contents of the contentcontrols as needed
private void HelpKeyListen(object sender, KeyEventArgs e)
{
if (e.Key == Key.F1)
{
HelpContent.Content = new Help();
}
}
Possibly create a new dependency property in ResizeableWindow if you want to provide main content from the outside. Lets say you add a dependency property (visual studio code snipped propdp) named MainContent, then you can bind it as follows:
<DockPanel x:Name="HelpPanel">
<ContentControl x:Name="HelpContent" DockPanel.Dock="Right"/>
<ContentControl x:Name="MainContentPlaceholder" Content="{Binding MainContent,RelativeSource={RelativeSource AnchestorType=Window}}"/>
</DockPanel>
The more appropriate option would be to replace the MainContentPlaceholder by some more WPF/MVVM friendly way to display your contents, but thats out of scope for the question.

Updating textbox from different classes in the application

I read many similar posts here but I still have some questions regarding not only how to accomplish this but if there is a better or more appropriate way to accomplish this. This being that I have a WPF application in which I have a Main window that instantiates a page object called ScratchPad that contains a textbox and a method to update the contents of that textbox.
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
ScratchPad scratchPad = new ScratchPad();
}
}
Here's the associated XAML that also instantiates(?) my other applications in frames within the MainWindow.
<DockPanel>
<TabControl TabStripPlacement="Left">
<TabItem Header="Main">
<Frame Source="Common/GUI/ScratchPad.xaml" ></Frame>
</TabItem>
<TabItem Header="Test Apps">
<Frame Source="Apps/TestApp/View/authPrompt_View.xaml" Margin="0,0,0,191.2" />
</TabItem>
<TabItem Header="Threads">
</TabItem>
</TabControl>
</DockPanel>
This object is intended to display log materials to report on the status of operations the application performs. The code for ScratchPad can be seen below.
public partial class ScratchPad : Page
{
public ScratchPad()
{
InitializeComponent();
}
public void updateStatus(string newText)
{
scratchPadTextBox.AppendText(newText);
}
}
My intention is to have many of my other classes be able to append to that textbox, however I believe in order to do this I would need to pass a reference to the MainWindow object to each of the classes that want to write to that textbox. My problem is that the other classes are not directly instantiated (to my knowledge) and as a result I'm not quite sure how to accomplish this or even if this is how it should be done. Here's a sample of a class that I would like to be able to append to the textbox which is created when the user hits submit on a page that is instantiated(?) through a frame source in the MainWindow's XAML.
class ConnectionManager
{
public void authenticateSharePoint(string urlAddress)
{
DataContextRef.DataContextRefDataContext dc =
new DataContextRef.DataContextRefDataContext(new Uri("redacted.svc"));
dc.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials; var source = dc.Test;
((System.Net.NetworkCredential)dc.Credentials).Domain = "blah";
((System.Net.NetworkCredential)dc.Credentials).UserName = "blah";
((System.Net.NetworkCredential)dc.Credentials).Password = "hardcodeisthebest123";
foreach (var item in source)
{
scratchPad.updateStatus("item.name: " + item.Name);
updateStatus("item.title: " + item.Title);
updateStatus("item.path: " + item.Path);
updateStatus("item.id: " + item.Id);
}
}
As seen in the XAML above copied again below, this class is created when a user selects the submit button in a separate class that is hosted in a frame in the MainWindow
<TabItem Header="Test App">
<Frame Source="Apps/TestApp/View/authPrompt_View.xaml" Margin="0,0,0,191.2" />
</TabItem>
As is always the case, when I type out my issue I realize just how many areas I need to address from a knowledge gap perspective. Any insight and/or assistance is appreciated!
however I believe in order to do this I would need to pass a reference to the MainWindow object to each of the classes that want to write to that textbox.
You could get a reference to the existing instance of the MainWindow using the Application.Current.Windows collection:
MainWindow mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
if(mainWindow != null)
{
//...
}
To be able to access your main view's object you have to create an instance of it from the class you want to access it from.
ScratchPad name = new ScratchPad();
Now you should be able to access the scratch pad with something like :
name.updateStatus.scratchPadTextBox

Application Navigation through a single frame

I am currently making an c# wpf application in which a task is need to do step-by-step through a single frame, like software installation. Has anyone got idea how to make it?
The way I did it on a recent application I made was to have the main window have 3 parts, a header, the main content, and a footer. Each one had a ContentControl that I bound so that in my ViewModel I could change the content being displayed dynamically. In the view model I loaded up the appropriate display based on the state of the program.
If I was at screen Welcome, I loaded up the appropriate header, footer, and content for the welcome screen. The XAML looked like this for each part:
<ContentControl x:Name="ContentHeader" Content="{Binding Path=ContentHeader}" Grid.Row="0"/>
<ContentControl x:Name="ContentMain" Content="{Binding Path=ContentMain}" Grid.Row="1"/>
<ContentControl x:Name="ContentFooter" Content="{Binding Path=ContentFooter}" Grid.Row="2"/>
In my code then I made new instances as necessary:
if (_screen == Constants.Screens.Welcome)
{
ContentHeader = new HeaderWelcome();
ContentMain = new MainWelcome();
ContentFooter = new FooterWelcome();
}
else if (_screen == Constants.Screens.Setup)
{
ContentHeader = new HeaderSetup();
ContentMain = new MainSetup();
ContentFooter = new FooterSetup();
}
Each part, MainWelcome, Main Setup, FooterWelcome, etc, are all just user controls I created.
This lets each of the parts have very little to do themselves. Buttons on those panels raised custom events to bubble up to the view model. Like so:
Define the custom event:
public static readonly RoutedEvent WelcomeEvent=
EventManager.RegisterRoutedEvent("WelcomeEvent", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(UserControl));
Call the custom event (from a button in this example):
private void ButtonWelcome_Click(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(Events.TestTintEvent));
}
Finally, tie the event to a function in your view model. This can be done in the MainWindow's constructor:
AddHandler(Events.WelcomeEvent, new RoutedEventHandler(ViewModel.GetInstance().Welcome));
Then I just have the function Welcome defined in my view model and do the work there:
public void Welcome(object sender, RoutedEventArgs e)
{
//Do Work
}
This may not be the most optimal way to do it, but it was how I did it, and it worked well in my situation.

WPF Page Within Tabcontrol TabItem Lazy loading

I have Xaml WPF window with TabControl inside. Each TabItem of it contains a page inside frame like this:
<TabItem >
<TabItem.Header>
Users
</TabItem.Header>
<Frame Source="/Views/UsersPage.xaml" />
</TabItem>
<TabItem>
<TabItem.Header>
Stats
</TabItem.Header>
<Frame Source="/Views/OrdersPage.xaml"/>
</TabItem>
The problem is when i open this window all pages inside frames loads simultaneously, so if , eg, I put MessageBox on each page load i got a lot of them. I want my pages to load only when I click on corresponding tab header.
How can i achieve this?
One possible solution would be to inherit from TabItem like this:
public class MyCustomTabItem : TabItem
{
public void MyCustomInitFunction()
{
//Do all your stuff here
}
}
In there put all your stuff that is supposed to be happening when the control gets clicked.
Create your TabControl with those new items:
<TabControl SelectionChanged="myTabControl_SelectionChanged" Name="myTabControl">
<MyCustomTabItem Name="myTab1">
<MyCustomTabItem.Header>
Users
</MyCustomTabItem.Header>
<Frame Source="/Views/UsersPage.xaml" />
</MyCustomTabItem>
<MyCustomTabItem Name="myTab2">
<MyCustomTabItem.Header>
Stats
</MyCustomTabItem.Header>
<Frame Source="/Views/OrdersPage.xaml"/>
</MyCustomTabItem>
</TabControl>
Hookup the SelectionChanged event handler to call your special init function:
private void myTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TabControl tc = (TabControl)e.Source;
if(tc != null)
{
MyCustomTabItem ti = (MyCustomTabItem)tc.Items[tc.SelectedIndex];
if(ti != null)
{
ti.MyCustomInitFunction()
}
}
}
If you only want this to happen the first time it get's shown put a bool variable in your MyCustomTabItem class that keeps track if it has already happened once, and if so just return instead of running it again.

Get event when user clicks away from current element or children

Okay, I have tried to use a popup to get this to work but there are a ton of reasons why that doesn't appear to be a route I want to take...especially because I've spent the last two hours trying to get it to work and I've deemed it unholier than all hell (this is despite the fact that I have popups in other places in the app that work just fine, but I digress...)
Basically I need only one piece of functionality that doesn't appear to be standard out of the box in WPF...I have to determine when someone clicks on something OTHER than a known UI element (I.E. they click away from something to close it...much like a popup set to StaysOpen = false)
From what I have gathered this is quite an arduous task and I can't seem to find a straight answer on the best way to do this...any ideas SO?
EDIT:
One of the commenters wanted me to post some sample code and re-reading through my question I really don't want to post something that is unrelated (the XY problem). I am posting this question for two reasons:
The onmouseleave event gets fired as soon as the popup opens. This means that if the popup is set to 'StaysOpen="False"' that the popup appears and immediately disappears no matter what. I believe wholeheartedly that this will not be an issue if I create a component that appears using the Visibility attribute to appear and disappear rather than placing it in a popup. The only reason I considered the popup component was because of it's StaysOpen=False functionality, not because it needs to float above everything else
The popup itself feels quite hacky, especially because it needs to fit inside of a parent component in the visual tree. As you can see from the code below, I have gotten the popup to fit inside of it's parent...but I really don't like binding a component's width and height to another component's actual width and height. This is the second reason I would like to avoid using a popup.
As a result, while this question could be 'how can I get the popup to work', the original question still stands: "How can I listen for a on click away event?" I would like to create a component that fits in the visual tree logically, and behaves as the following:
On hover over a component, appear
On leave a component disappear
On click on a component persist appearing
On click away from a component or itself close
I have all of the above handled except for on click away
How about the UIElement.LostFocus-Event? That seems to be the one you need.
I think in this case, you can be useful routed events. There are two types of events: Bubbling, Direct and Tunneling. Attention should be paid to Bubbling and Tunneling. Bubbling events rises up the logical tree and tunneling down. Below is a diagram from here:
So that event up / down the tree, it should be set on each control. Usually, the demonstration bubbling events, apply this example:
XAML
<Window x:Class="DemoRoutedEvents.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" MouseUp="somethingClicked">
<Grid MouseUp="somethingClicked">
<StackPanel MouseUp="somethingClicked" Margin="0,0,10,0">
<Label x:Name="btnClickMe" Content="Click Me!" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="101,22,0,0" MouseUp="somethingClicked"/>
<CheckBox x:Name="chkhandle" Content="CheckBox" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="241,28,0,0" RenderTransformOrigin="-0.588,1.188"/>
<ListBox x:Name="lstEvents" HorizontalAlignment="Left" Height="604" VerticalAlignment="Top" Width="416" Margin="29,66,0,0"/>
</StackPanel>
</Grid>
</Window>
Code behind
public int eventCounter = 0;
private void somethingClicked(object sender, RoutedEventArgs e)
{
eventCounter++;
String message = "#" + eventCounter.ToString() + ":\r\n" +
" Sender: " + sender.ToString() + ":\r\n" +
" Source: " + e.Source + ":\r\n" +
" Original Source: " + e.OriginalSource;
lstEvents.Items.Add(message);
e.Handled = (bool)chkhandle.IsChecked;
if (e.Handled)
lstEvents.Items.Add("Completed");
}
Output
I tried to optimize this process for multiple panels and components. I have created a attached dependency property IsDebugEvent, which is in the class of EventBehaviours. The principle is simple, we take an event handler and set it for all elements of the type Control (almost all the UIElements it inherits). For panels such as a Grid, StackPanel, WrapPanel, etc, Panel is the base class.
In the handler, we find ListBox and display the name of the panel s the element that caused the event, just for test. The example uses the event PreviewMouseLeftButtonDown (tunneling), because the first fires is an event at the Button.Click, and when it works, it conflicts with the event MouseUp. Quote from here:
ButtonBase inherits from UIElement, a Button will also have access to all of the mouse button events defined for UIElement. Because the Button does something in response to button presses, it swallows the bubbling events (e.g. MouseLeftButtonDown and MouseDown). You can still detect these lower level button press events by adding handlers for the tunneling events (e.g. PreviewMouseLeftButtonDown and PreviewMouseDown).
XAML
<Window x:Class="AwayEventHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AwayEventHelp"
Title="MainWindow" Height="550" Width="525"
WindowStartupLocation="CenterScreen">
<Grid>
<CheckBox Name="DebugCheckBox" Width="100" Height="30"
VerticalAlignment="Top"
Content="Debug event" IsChecked="False"
Checked="DebugCheckBox_Checked" Unchecked="DebugCheckBox_Unchecked" />
<StackPanel Name="LeftStackPanel" Width="150" local:EventBehaviours.IsDebugEvent="False"
HorizontalAlignment="Left" Background="BlanchedAlmond">
<Button Name="LeftButton1" Height="30" Content="LeftButton1" />
<Button Name="LeftButton2" Height="30" Content="LeftButton2" />
<Button Name="LeftButton3" Height="30" Content="LeftButton3" />
<Label Name="JustLabelLeft" Content="JustLabelLeft" Background="Azure" HorizontalContentAlignment="Center" />
</StackPanel>
<StackPanel Name="RightStackPanel" Width="150" local:EventBehaviours.IsDebugEvent="False"
HorizontalAlignment="Right" Background="Azure">
<Button Name="RightButton1" Height="30" Content="RightButton1" />
<Button Name="RightButton2" Height="30" Content="RightButton2" />
<Button Name="RightButton3" Height="30" Content="RightButton3" />
<Label Name="JustLabelRight" Content="JustLabelRight" Background="BlanchedAlmond" HorizontalContentAlignment="Center" />
</StackPanel>
<Grid Name="GridPanel" Width="100" Height="100" local:EventBehaviours.IsDebugEvent="False"
VerticalAlignment="Bottom" Background="CadetBlue">
<Label Name="LabelInGrid" Width="100" Height="50" Content="LabelInGrid" Background="AliceBlue" />
</Grid>
<ListBox Name="EventOutput" Width="180" Height="180" Background="AliceBlue" />
</Grid>
</Window>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void DebugCheckBox_Checked(object sender, RoutedEventArgs e)
{
EventBehaviours.SetIsDebugEvent(LeftStackPanel, true);
EventBehaviours.SetIsDebugEvent(RightStackPanel, true);
EventBehaviours.SetIsDebugEvent(GridPanel, true);
}
private void DebugCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
EventBehaviours.SetIsDebugEvent(LeftStackPanel, false);
EventBehaviours.SetIsDebugEvent(RightStackPanel, false);
EventBehaviours.SetIsDebugEvent(GridPanel, false);
}
}
public class EventBehaviours : DependencyObject
{
#region IsDebugEvent declaration
public static void SetIsDebugEvent(DependencyObject target, bool value)
{
target.SetValue(IsDebugEventProperty, value);
}
public static bool GetIsDebugEvent(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsDebugEventProperty);
}
public static readonly DependencyProperty IsDebugEventProperty =
DependencyProperty.RegisterAttached("IsDebugEvent",
typeof(bool),
typeof(EventBehaviours),
new UIPropertyMetadata(false, OnIsDebugEvent));
#endregion
private static void OnIsDebugEvent(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Panel MyPanel = sender as Panel;
if (e.NewValue is bool && ((bool)e.NewValue == true))
{
MyPanel.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(MyControl_PreviewMouseLeftButtonDown);
if (MyPanel.Children.Count != 0)
{
foreach (Control MyControl in MyPanel.Children)
{
MyControl.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(MyControl_PreviewMouseLeftButtonDown);
}
}
}
else
{
foreach (Control MyControl in MyPanel.Children)
{
MyControl.PreviewMouseLeftButtonDown -= new MouseButtonEventHandler(MyControl_PreviewMouseLeftButtonDown);
}
MyPanel.PreviewMouseLeftButtonDown -= new MouseButtonEventHandler(MyControl_PreviewMouseLeftButtonDown);
}
}
/// <summary>
/// Main handler of PreviewMouseLeftButtonDown event
/// </summary>
private static void MyControl_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
string OutInfo = string.Empty;
if (sender.GetType() == typeof(StackPanel))
{
StackPanel MyStackPanel = sender as StackPanel;
Grid MyGrid = MyStackPanel.Parent as Grid;
OutInfo = "PanelName: " + MyStackPanel.Name;
OutInfoInListBox(MyGrid, OutInfo);
}
else if (sender.GetType() == typeof(Grid))
{
Grid MyGrid = sender as Grid;
Grid MyMainGrid = MyGrid.Parent as Grid;
OutInfo = "PanelName: " + MyGrid.Name;
OutInfoInListBox(MyMainGrid, OutInfo);
}
else
{
Control MyControl = sender as Control;
Panel MyStackPanel = MyControl.Parent as Panel;
Grid MyGrid = MyStackPanel.Parent as Grid;
OutInfo = "ControlName: " + MyControl.Name;
OutInfoInListBox(MyGrid, OutInfo);
}
}
/// <summary>
/// Get ListBox and insert some info
/// </summary>
/// <param name="ParentGrid">Panel, where locate ListBox</param>
/// <param name="info">Just string</param>
private static void OutInfoInListBox(Grid ParentGrid, string info)
{
ListBox MyEventOutput = ParentGrid.FindName("EventOutput") as ListBox;
MyEventOutput.Items.Add(info);
}
}
Output
By clicking on the CheckBox, set a dependency property IsDebugEvent in True, subject thus causing OnIsDebugEvent, where we set the handlers. If you deselect the CheckBox in, then all event handlers deleted.
To set the events immediately on startup, you need to make sure that all the items on the successfully booted. This can be done in the event ContentRendered of Window.

Categories