Updating textbox from different classes in the application - c#

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

Related

Programmatically switching tabs in a Tab Control

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.

Dynamically coupling XAML with code-behind

A little background information
I am learning Xamarin.Forms and am currently struggling a bit with dynamically coupling my ContentPage's XAML with my code-behind. Obviously, I am at the mercy of my complete unawareness of how Xamarin.Form's should be written, so I hope you can bare with my slight confusion.
I am developing a mobile application for Android and am using the BottomNavigationBarXF to put the navigation bar at the bottom, which is working well. Currently, I am using the example project for my learning.
The actual problem
I have created a series of ContentPage's which I would like to dynamically couple upon instantiating each new page. My ContentPage's have corresponding code-behind, which I have left untouched; e.g., I have a ContentPage named HomePage, which have this code-behind:
namespace BottomBarXFExample
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
}
}
}
and this corresponding XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BottomBarXFExample.HomePage">
<ContentPage.Content>
<StackLayout>
<Label Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
The way I go about creating pages is as follows.
string[] tabTitles = { "Me", "Trends", "Home", "Plan", "About" };
ContentPage[] pages = new ContentPage[tabTitles.Length];
for (int i = 0; i < tabTitles.Length; ++i)
{
ContentPage page = createPage(tabTitles[i]);
bottomBarPage.Children.Add(page);
}
The createPage method:
private ContentPage createPage(String title)
{
FileImageSource icon = setIcon(title);
ContentPage page = new ContentPage()
{
Title = title,
Icon = icon,
};
// should something happen here with the XAML?
return page;
}
And the setIcon method:
private FileImageSource setIcon(String title)
{
FileImageSource icon = (FileImageSource)FileImageSource.FromFile(
string.Format(
"ic_" + title.ToLowerInvariant() + ".png",
title.ToLowerInvariant()
));
return icon;
}
Using this approach I am successful in creating the bottom navigation bar. However, navigating to each page using the navigation bar, the view is "obviously" empty, because I am not linking the ContentPage to its corresponding XAML. Can this be done in code?
If I opt for instantiating each ContentPage the "right" way:
HomePage homePage = new HomePage()
{
Title = "Home",
Icon = homeIcon
};
And then add them to the navigation bar like so:
bottomBarPage.Children.Add(homePage)
I do obtain coupling between XAML and code-behind. However, I find it rather tedious, and probably also unnecessary, to do it this way.
Any suggestions?
Thank you,
Kris
Xaml pages and code behind classes are tightly coupled in xaml file with x:Class definition. Xaml pages cannot be inherited, but ContentPage classes can but I don't see that resolving your issues. If you're looking after having only one xaml page then you'd have to create the rendering logic in code behind, e.g.
public HomePage(string title)
{
InitializeComponent();
switch(title)
{
// set Binding Context to your VM
... BindingContext = titleBasedVM;
}
}
Your VM can then contain page specific data. This concept uses MVVM, which is highly recommended when using Xamarin Forms.
Also take a look at ControlTemplate for rendering generic page sections.
You shouldn't try to generate xaml dynamically as it's not supported.

Can I access XAML elements in an array in the codebehind?

I've been looking around but I haven't been able to find anything on this. I am trying to get started making Windows 8.1 apps in C# with Visual Studio 2013 Pro. I want to be able to access multiple elements (particularly buttons or text blocks) in an array because this is more convenient for developing things like board games. For instance, if I were developing tic-tac-toe, I might use a series of buttons like this:
<Grid>
<Button Name="Cell00"/>
<Button Name="Cell01"/>
<Button Name="Cell02"/>
<Button Name="Cell10"/>
<Button Name="Cell11"/>
<Button Name="Cell12"/>
<Button Name="Cell20"/>
<Button Name="Cell21"/>
<Button Name="Cell22"/>
<Grid/>
Now for the function that would check for a win, I would have to check all possible combinations like this is in the code behind:
private bool CheckForWin()
{
if((Cell00 == Cell01) && (Cell01 == Cell02) && isNotBlank(Cell02)) return true;
if((Cell10 == Cell11) && (Cell11 == Cell12) && isNotBlank(Cell12)) return true
...
return false; //if none of the win conditions pass
}
This type of code would be extremely cumbersome. I would like to write it instead in a way that lets me check the array with for loops.
I realize that with tic-tac-toe, it is fairly easy to code it using brute force, but this was the first example that came to my head. Other games like Reversi or Go would not work well like this because of either the sheer size or the fact that pieces placed can change other cells than the one they were placed on.
Any help with this would be greatly appreciated.
This is not the correct way to use WPF. WPF is designed to use data binding....creating and manipulating UI elements directly is bad form. There are more posts/discussion/questions about this than you can imagine and I'll leave you to research them for yourself. In the mean time this is how you use WPF "properly":
First use NuGet to add MVVM lite to your project so that you get the ViewModelBase class and create a view model for a single cell:
public class Cell : ViewModelBase
{
private string _Text;
public string Text
{
get { return _Text; }
set { _Text = value; RaisePropertyChanged(() => this.Text); }
}
}
One level up you'll want a main model to encapsulate an array of these, this is where you will typically do all your game logic:
public class MainModel : ViewModelBase
{
private ObservableCollection<Cell> _Cells;
public ObservableCollection<Cell> Cells
{
get { return _Cells; }
set { _Cells = value; RaisePropertyChanged(() => this.Cells); }
}
public MainModel()
{
this.Cells = new ObservableCollection<Cell>(
Enumerable.Range(1, 100)
.Select(i => new Cell { Text = i.ToString() })
);
}
}
Notice that all I'm doing at the moment is creating a 100-element collection of cells. This main view model becomes the one that you assign to your window's data context:
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainModel();
}
Now your XAML controls need to bind to this data. ItemsControl is used to render a collection of elements so use one of those and bind it to your array. You want them displayed in a 2D grid so replace the ItemsPanelTemplate with a WrapPanel. Finally add a DataTemplate for your Cell class so that a button gets drawn for each cell:
<Window.Resources>
<DataTemplate DataType="{x:Type local:Cell}">
<Button Width="32" Height="32" Content="{Binding Text}"/>
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Cells}" Width="320" Height="320" HorizontalAlignment="Center" VerticalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
That's how you use WPF. Your logic is stored entirely in the view model and it's completely decoupled from the view. Here's what this particular code displays, it should be pretty self-evident how flexible this code is and easy to change:
That's very possible. Simply declare an array variable :
private Button[] _buttonArray;
populate the array once, maybe in constructor :
_buttonArray = new[] {Cell00, Cell01, .... , Cell22};
And all of the buttons are now accessible through _buttonArray.

Binding resp. DataTemplate confusion

I am still very new and trying my first serious data binding. I have read a lot about how it works, am just struggling with this concrete example. I have tried to read all links I could find on this, but most sources tend to be a bit imprecise at key spots. So here goes:
-My Application generates dynamically a variable 'PlayerList' of type 'List', where 'Player' is a complex object.
-I want to display this in a ListBox via Binding. Obvoiusly, since Player is a complex Object I want to create a DataTemplate for it. So I have something like this in the 'Window1.xaml':
<ListBox
Name="ListBox_Players"
ItemsSource="{Binding Source={StaticResource PlayerListResource}}"
ItemTemplate="{StaticResource PlayerTemplate}">
</ListBox>
and something like this in the 'App.xaml':
<DataTemplate x:Key="PlayerTemplate"> <!-- DataType="{x:Type Player}" -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=name}"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding Path=nrOfTabls}"/>
</StackPanel>
</DataTemplate>
Of course, this template will become more verbose later. So as you see above, I have tried to create a resource for the PlayerList variable, but have not managed yet, i.e., smthn. like this
<src: XXX x:Key="PlayerListResource"/>
where for XXX as I understand it I should enter the class of the Resource variable. I tried
List<Player>, List<src:Player>
etc., but obv. XAML has trouble with the '<,>' characters.
I also have another problem: By not declaring a resource but by direct binding (i.e., in C# writing "ListBox_Players.ItemsSource=PlayerList;") and deleting the 'ItemTemplate' declaration and overwriting the ToString() method of the Player class to output the name of the Player I have managed to see that the binding works (i.e., I get a list of Player names in the ListBox). But then, if I insert the template again, it displays only ','my Template does not work!
The fact that you're getting just commas without anything else suggests to me that either the names of Player members do not match the names in Path= in the DataTemplate (I had this problem at one point), or the relevant Player members are inaccessible.
I just tested what you've shown of your code so far, and it seemed to work fine. The only change I made was change this line:
ItemsSource="{Binding Source={StaticResource PlayerListResource}}"
to this line:
ItemsSource = "{Binding}"
This tells the program that it'll get the ItemsSource at run time.
My Player class was:
class Player {
public string name { get; set; }
public int nrOfTabls { get; set; }
}
and my MainWindow.xaml.cs was:
public partial class MainWindow : Window {
private ObservableCollection<Player> players_;
public MainWindow() {
InitializeComponent();
players_ =new ObservableCollection<Player> () {
new Player() {
name = "Alex",
nrOfTabls = 1,
},
new Player() {
name = "Brett",
nrOfTabls = 2,
},
new Player() {
name="Cindy",
nrOfTabls = 231,
}
};
ListBox_Players.ItemsSource = players_;
}
}

Passing application state between viewmodels in MVVM WPF application

I need to write a small application to read a configuration file and generate some report with it. I was hoping to finally use MVVM but it's quite tricky to get started. Oh, I'm using Caliburn.Micro framework.
So this is what I have, a shell (primary view that hosts other views) that has a ribbon with 3 buttons on it:
1) Open file
2) Show settings
3) Show results
And two other views, SettingsView and ResultsView with buttons to generate and delete a report.
So I guess the view structure would be like this:
ShellView
Ribbon
OpenFileButton
SettingsButton
ResultsButton
ContentControl (hosts SettingsView and ResultsView)
SettingsView
CalculateResultsButton
ResultsView
CancelResultsButton
The tricky part is this:
1. "Show settings" button is disabled until a file is opened (via Open file).
2. "Show results" button is disabled until a report is calculated (via a
method in SettingsViewModel).
3. If a report is calculated, the CalculateResultsButton is disabled and
CancelResultsButton is enabled and vice versa.
Please advise how could I achieve this ? I've no ideas what strategy should I go for. My non-MVVM-thinking-brain says that I should create a status variable and then somehow bind those buttons to that variable, but I guess that wont work in a MVVM world, right ? Any code example would be very very very appreciated!
Many thanks!
Since you're using CM you won't need any code-behind. You can delete the .xaml.cs files if you want.
This is a pretty basic example but it should give you an idea on how to control the state of the buttons. In this example, Open will be enabled and the other two are disabled. If you click on Open, Settings is enabled. The same happens with Results once Settings is clicked.
If you need a way to do global state the same concept can be applied by injecting a singleton, SharedViewModel, into the ViewModels and the CanXXX methods can check values in SharedViewModel. This is a SL demo of different things but one is injecting a singleton to share data, the same idea applies in wpf.
ShellView:
<Window x:Class="CMWPFGuardSample.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Orientation="Horizontal">
<Button x:Name="Open"
Content="Open" />
<Button x:Name="Settings"
Content="Settings" />
<Button x:Name="Results"
Content="Results" />
</StackPanel>
</Grid>
</Window>
ShellViewModel:
[Export(typeof (IShell))]
public class ShellViewModel : PropertyChangedBase, IShell
{
private bool _isOpen;
public bool IsOpen
{
get { return _isOpen; }
set
{
_isOpen = value;
NotifyOfPropertyChange(() => IsOpen);
NotifyOfPropertyChange(() => CanSettings);
}
}
private bool _isSettings;
public bool IsSettings
{
get { return _isSettings; }
set
{
_isSettings = value;
NotifyOfPropertyChange(() => IsSettings);
NotifyOfPropertyChange(() => CanResults);
}
}
public bool IsResults { get; set; }
public void Open()
{
IsOpen = true;
}
public bool CanSettings
{
get { return IsOpen; }
}
public void Settings()
{
IsSettings = true;
}
public bool CanResults
{
get { return IsSettings; }
}
public void Results()
{
}
}
MVVM and WPF Commands perfectly fits your "tricky part" requirements since have built in ICommand.CanExecute() method which allows enabling/disabling corresponding button based on custom logic.
To use this naice feature take a look first at the RoutedCommand Class and self explanatory example on MSDN How to: Enable a Command (see below code snippets).
And in general about MVVM, it is really SIMPLE! Just try it and you won't leave without it ;) In few words - you have to create for each EntityView.xaml corresponding EntityViewModel class and then just put instance of it in the View's DataContext either explicitly in code or using bindings:
var entityViewModel = new EntityViewModel();
var view = new EntityView();
view.DataContext = entityViewModel;
MVVM Command and Command.CanExecute bindings:
XAML:
<Window x:Class="WCSamples.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CloseCommand"
Name="RootWindow"
>
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close"
Executed="CloseCommandHandler"
CanExecute="CanExecuteHandler"
/>
</Window.CommandBindings>
<StackPanel Name="MainStackPanel">
<Button Command="ApplicationCommands.Close"
Content="Close File" />
</StackPanel>
</Window>
C# code behind:
// Create ui elements.
StackPanel CloseCmdStackPanel = new StackPanel();
Button CloseCmdButton = new Button();
CloseCmdStackPanel.Children.Add(CloseCmdButton);
// Set Button's properties.
CloseCmdButton.Content = "Close File";
CloseCmdButton.Command = ApplicationCommands.Close;
// Create the CommandBinding.
CommandBinding CloseCommandBinding = new CommandBinding(
ApplicationCommands.Close, CloseCommandHandler, CanExecuteHandler);
// Add the CommandBinding to the root Window.
RootWindow.CommandBindings.Add(CloseCommandBinding);

Categories