I want the user to select a directory where a file that I will then generate will be saved. I know that in WPF I should use the OpenFileDialog from Win32, but unfortunately the dialog requires file(s) to be selected - it stays open if I simply click OK without choosing one. I could "hack up" the functionality by letting the user pick a file and then strip the path to figure out which directory it belongs to but that's unintuitive at best. Has anyone seen this done before?
You can use the built-in FolderBrowserDialog class for this. Don't mind that it's in the System.Windows.Forms namespace.
using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
{
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
}
If you want the window to be modal over some WPF window, see the question How to use a FolderBrowserDialog from a WPF application.
EDIT: If you want something a bit more fancy than the plain, ugly Windows Forms FolderBrowserDialog, there are some alternatives that allow you to use the Vista dialog instead:
Third-party libraries, such as Ookii dialogs (.NET 4.5+)
The Windows API Code Pack-Shell:
using Microsoft.WindowsAPICodePack.Dialogs;
...
var dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = true;
CommonFileDialogResult result = dialog.ShowDialog();
Note that this dialog is not available on operating systems older than Windows Vista, so be sure to check CommonFileDialog.IsPlatformSupported first.
I created a UserControl which is used like this:
<UtilitiesWPF:FolderEntry Text="{Binding Path=LogFolder}" Description="Folder for log files"/>
The xaml source looks like this:
<UserControl x:Class="Utilities.WPF.FolderEntry"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<Button Margin="0" Padding="0" DockPanel.Dock="Right" Width="Auto" Click="BrowseFolder">...</Button>
<TextBox Height="Auto" HorizontalAlignment="Stretch" DockPanel.Dock="Right"
Text="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
</DockPanel>
</UserControl>
and the code-behind
public partial class FolderEntry {
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(FolderEntry), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(FolderEntry), new PropertyMetadata(null));
public string Text { get { return GetValue(TextProperty) as string; } set { SetValue(TextProperty, value); }}
public string Description { get { return GetValue(DescriptionProperty) as string; } set { SetValue(DescriptionProperty, value); } }
public FolderEntry() { InitializeComponent(); }
private void BrowseFolder(object sender, RoutedEventArgs e) {
using (FolderBrowserDialog dlg = new FolderBrowserDialog()) {
dlg.Description = Description;
dlg.SelectedPath = Text;
dlg.ShowNewFolderButton = true;
DialogResult result = dlg.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK) {
Text = dlg.SelectedPath;
BindingExpression be = GetBindingExpression(TextProperty);
if (be != null)
be.UpdateSource();
}
}
}
}
As stated in earlier answers, FolderBrowserDialog is the class to use for this. Some people have (justifiable) concerns with the appearance and behaviour of this dialog. The good news is that it was "modernized" in NET Core 3.0, so is now a viable option for those writing either Windows Forms or WPF apps targeting that version or later (you're out of luck if still using NET Framework though).
In .NET Core 3.0, Windows Forms users [sic] a newer COM-based control that was introduced in Windows Vista:
To reference System.Windows.Forms in a NET Core WPF app, it is necessary to edit the project file and add the following line:
<UseWindowsForms>true</UseWindowsForms>
This can be placed directly after the existing <UseWPF> element.
Then it's just a case of using the dialog:
using System;
using System.Windows.Forms;
...
using var dialog = new FolderBrowserDialog
{
Description = "Time to select a folder",
UseDescriptionForTitle = true,
SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)
+ Path.DirectorySeparatorChar,
ShowNewFolderButton = true
};
if (dialog.ShowDialog() == DialogResult.OK)
{
...
}
FolderBrowserDialog has a RootFolder property that supposedly "sets the root folder where the browsing starts from" but whatever I set this to it didn't make any difference; SelectedPath seemed to be the better property to use for this purpose, however the trailing backslash is required.
Also, the ShowNewFolderButton property seems to be ignored as well, the button is always shown regardless.
Ookii folder dialog can be found at Nuget.
PM> Install-Package Ookii.Dialogs.Wpf
And, example code is as below.
var dialog = new Ookii.Dialogs.Wpf.VistaFolderBrowserDialog();
if (dialog.ShowDialog(this).GetValueOrDefault())
{
textBoxFolderPath.Text = dialog.SelectedPath;
}
More information on how to use it: https://github.com/augustoproiete/ookii-dialogs-wpf
For those who don't want to create a custom dialog but still prefer a 100% WPF way and don't want to use separate DDLs, additional dependencies or outdated APIs, I came up with a very simple hack using the Save As dialog.
No using directive needed, you may simply copy-paste the code below !
It should still be very user-friendly and most people will never notice.
The idea comes from the fact that we can change the title of that dialog, hide files, and work around the resulting filename quite easily.
It is a big hack for sure, but maybe it will do the job just fine for your usage...
In this example I have a textbox object to contain the resulting path, but you may remove the related lines and use a return value if you wish...
// Create a "Save As" dialog for selecting a directory (HACK)
var dialog = new Microsoft.Win32.SaveFileDialog();
dialog.InitialDirectory = textbox.Text; // Use current value for initial dir
dialog.Title = "Select a Directory"; // instead of default "Save As"
dialog.Filter = "Directory|*.this.directory"; // Prevents displaying files
dialog.FileName = "select"; // Filename will then be "select.this.directory"
if (dialog.ShowDialog() == true) {
string path = dialog.FileName;
// Remove fake filename from resulting path
path = path.Replace("\\select.this.directory", "");
path = path.Replace(".this.directory", "");
// If user has changed the filename, create the new directory
if (!System.IO.Directory.Exists(path)) {
System.IO.Directory.CreateDirectory(path);
}
// Our final value is in path
textbox.Text = path;
}
The only issues with this hack are :
Acknowledge button still says "Save" instead of something like "Select directory", but in a case like mines I "Save" the directory selection so it still works...
Input field still says "File name" instead of "Directory name", but we can say that a directory is a type of file...
There is still a "Save as type" dropdown, but its value says "Directory (*.this.directory)", and the user cannot change it for something else, works for me...
Most people won't notice these, although I would definitely prefer using an official WPF way if microsoft would get their heads out of their asses, but until they do, that's my temporary fix.
Ookii Dialogs includes a dialog for selecting a folder (instead of a file):
https://github.com/ookii-dialogs
For Directory Dialog to get the Directory Path, First Add reference System.Windows.Forms, and then Resolve, and then put this code in a button click.
var dialog = new FolderBrowserDialog();
dialog.ShowDialog();
folderpathTB.Text = dialog.SelectedPath;
(folderpathTB is name of TextBox where I wana put the folder path, OR u can assign it to a string variable too i.e.)
string folder = dialog.SelectedPath;
And if you wana get FileName/path, Simply do this on Button Click
FileDialog fileDialog = new OpenFileDialog();
fileDialog.ShowDialog();
folderpathTB.Text = fileDialog.FileName;
(folderpathTB is name of TextBox where I wana put the file path, OR u can assign it to a string variable too)
Note: For Folder Dialog, the System.Windows.Forms.dll must be added to the project, otherwise it wouldn't work.
I found the below code on below link... and it worked
Select folder dialog WPF
using Microsoft.WindowsAPICodePack.Dialogs;
var dlg = new CommonOpenFileDialog();
dlg.Title = "My Title";
dlg.IsFolderPicker = true;
dlg.InitialDirectory = currentDirectory;
dlg.AddToMostRecentlyUsedList = false;
dlg.AllowNonFileSystemItems = false;
dlg.DefaultDirectory = currentDirectory;
dlg.EnsureFileExists = true;
dlg.EnsurePathExists = true;
dlg.EnsureReadOnly = false;
dlg.EnsureValidNames = true;
dlg.Multiselect = false;
dlg.ShowPlacesList = true;
if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
{
var folder = dlg.FileName;
// Do something with selected folder string
}
I'd suggest, to add in the nugget package:
Install-Package OpenDialog
Then the way to used it is:
Gat.Controls.OpenDialogView openDialog = new Gat.Controls.OpenDialogView();
Gat.Controls.OpenDialogViewModel vm = (Gat.Controls.OpenDialogViewModel)openDialog.DataContext;
vm.IsDirectoryChooser = true;
vm.Show();
WPFLabel.Text = vm.SelectedFilePath.ToString();
Here's the documentation:
http://opendialog.codeplex.com/documentation
Works for Files, files with filter, folders, etc
The best way to achieve what you want is to create your own wpf based control , or use a one that was made by other people
why ? because there will be a noticeable performance impact when using the winforms dialog in a wpf application (for some reason)
i recommend this project
https://opendialog.codeplex.com/
or Nuget :
PM> Install-Package OpenDialog
it's very MVVM friendly and it isn't wraping the winforms dialog
The Ookii VistaFolderBrowserDialog is the one you want.
If you only want the Folder Browser from Ooki Dialogs and nothing else then download the Source, cherry-pick the files you need for the Folder browser (hint: 7 files) and it builds fine in .NET 4.5.2. I had to add a reference to System.Drawing. Compare the references in the original project to yours.
How do you figure out which files? Open your app and Ookii in different Visual Studio instances. Add VistaFolderBrowserDialog.cs to your app and keep adding files until the build errors go away. You find the dependencies in the Ookii project - Control-Click the one you want to follow back to its source (pun intended).
Here are the files you need if you're too lazy to do that ...
NativeMethods.cs
SafeHandles.cs
VistaFolderBrowserDialog.cs
\ Interop
COMGuids.cs
ErrorHelper.cs
ShellComInterfaces.cs
ShellWrapperDefinitions.cs
Edit line 197 in VistaFolderBrowserDialog.cs unless you want to include their Resources.Resx
throw new InvalidOperationException(Properties.Resources.FolderBrowserDialogNoRootFolder);
throw new InvalidOperationException("Unable to retrieve the root folder.");
Add their copyright notice to your app as per their license.txt
The code in \Ookii.Dialogs.Wpf.Sample\MainWindow.xaml.cs line 160-169 is an example you can use but you will need to remove this, from MessageBox.Show(this, for WPF.
Works on My Machine [TM]
None of these answers worked for me (generally there was a missing reference or something along those lines)
But this quite simply did:
Using FolderBrowserDialog in WPF application
Add a reference to System.Windows.Forms and use this code:
var dialog = new System.Windows.Forms.FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
No need to track down missing packages. Or add enormous classes
This gives me a modern folder selector that also allows you to create a new folder
I'm yet to see the impact when deployed to other machines
I know this is an old question, but a simple way to do this is use the FileDialog option provided by WPF and using System.IO.Path.GetDirectory(filename).
You could use smth like this in WPF. I've created example method.
Check below.
public string getFolderPath()
{
// Create OpenFileDialog
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Multiselect = false;
openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
if (openFileDialog.ShowDialog() == true)
{
System.IO.FileInfo fInfo = new System.IO.FileInfo(openFileDialog.FileName);
return fInfo.DirectoryName;
}
return null;
}
It seems that the Microsoft.Win32 .NET library does not support selecting folders (only files), so you are out of luck in WPF (as of 7/2022). I feel the best option now is Ookii for WPF: https://github.com/ookii-dialogs/ookii-dialogs-wpf. It works great and as expected in WPF minus Microsoft support. You can get it as a NuGet package. Code behind XAML View:
public partial class ExportRegionView : UserControl
{
public ExportRegionView()
{
InitializeComponent();
}
private void SavePath(object sender, RoutedEventArgs e)
{
var dialog = new Ookii.Dialogs.Wpf.VistaFolderBrowserDialog();
dialog.Description = "SIPAS Export Folder";
dialog.UseDescriptionForTitle = true;
if (dialog.ShowDialog().GetValueOrDefault())
{
ExportPath.Text = dialog.SelectedPath;
}
}
}
XAML: <Button Grid.Row="1" Grid.Column="3" Style="{DynamicResource Esri_Button}" Click="SavePath" Margin="5,5,5,5">Path</Button>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Gearplay
{
/// <summary>
/// Логика взаимодействия для OpenFolderBrows.xaml
/// </summary>
public partial class OpenFolderBrows : Page
{
internal string SelectedFolderPath { get; set; }
public OpenFolderBrows()
{
InitializeComponent();
Selectedpath();
InputLogicalPathCollection();
}
internal void Selectedpath()
{
Browser.Navigate(#"C:\");
Browser.Navigated += Browser_Navigated;
}
private void Browser_Navigated(object sender, NavigationEventArgs e)
{
SelectedFolderPath = e.Uri.AbsolutePath.ToString();
//MessageBox.Show(SelectedFolderPath);
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
}
string [] testing { get; set; }
private void InputLogicalPathCollection()
{ // add Menu items for Cotrol
string[] DirectoryCollection_Path = Environment.GetLogicalDrives(); // Get Local Drives
testing = new string[DirectoryCollection_Path.Length];
//MessageBox.Show(DirectoryCollection_Path[0].ToString());
MenuItem[] menuItems = new MenuItem[DirectoryCollection_Path.Length]; // Create Empty Collection
for(int i=0;i<menuItems.Length;i++)
{
// Create collection depend how much logical drives
menuItems[i] = new MenuItem();
menuItems[i].Header = DirectoryCollection_Path[i];
menuItems[i].Name = DirectoryCollection_Path[i].Substring(0,DirectoryCollection_Path.Length-1);
DirectoryCollection.Items.Add(menuItems[i]);
menuItems[i].Click += OpenFolderBrows_Click;
testing[i]= DirectoryCollection_Path[i].Substring(0, DirectoryCollection_Path.Length - 1);
}
}
private void OpenFolderBrows_Click(object sender, RoutedEventArgs e)
{
foreach (string str in testing)
{
if (e.OriginalSource.ToString().Contains("Header:"+str)) // Navigate to Local drive
{
Browser.Navigate(str + #":\");
}
}
}
private void Goback_Click(object sender, RoutedEventArgs e)
{// Go Back
try
{
Browser.GoBack();
}catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void Goforward_Click(object sender, RoutedEventArgs e)
{ //Go Forward
try
{
Browser.GoForward();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void FolderForSave_Click(object sender, RoutedEventArgs e)
{
// Separate Click For Go Back same As Close App With send string var to Main Window ( Main class etc.)
this.NavigationService.GoBack();
}
}
}
Related
I have seen several posts on various forms varying in times from 2006 to now about how to add hyperlinks to RichTextBox, but they all seem overly complex for what I want. I am creating a desktop chat client, and I receive input as strings, now among these strings may be some urls, I need those urls to be clickable. Which from what I gather means they need to be HyperLink objects.
Navigating through the RichTextBox and replacing the urls with HyperLinks seems to be no small feat. Does anyone have a relatively simple solution for this?
In my web client it's a simple one liner
value = value.replace(/(http:\/\/[^\s]+)/gi, '$1');
Never thought I'd see the day where C# actually makes it harder.
If you want to do an equivalent of value.replace(/(http:\/\/[^\s]+)/gi, '$1') in WPF:
<RichTextBox x:Name="MyRichTextBox" IsDocumentEnabled="True" IsReadOnly="True" />
And the code that converts the string is the following:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var htmlText = "Google's website is http://www.google.com";
MyRichTextBox.Document = ConvertToFlowDocument(htmlText);
}
private FlowDocument ConvertToFlowDocument(string text)
{
var flowDocument = new FlowDocument();
var regex = new Regex(#"(http:\/\/[^\s]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
var matches = regex.Matches(text).Cast<Match>().Select(m => m.Value).ToList();
var paragraph = new Paragraph();
flowDocument.Blocks.Add(paragraph);
foreach (var segment in regex.Split(text))
{
if (matches.Contains(segment))
{
var hyperlink = new Hyperlink(new Run(segment))
{
NavigateUri = new Uri(segment),
};
hyperlink.RequestNavigate += (sender, args) => Process.Start(segment);
paragraph.Inlines.Add(hyperlink);
}
else
{
paragraph.Inlines.Add(new Run(segment));
}
}
return flowDocument;
}
}
It uses the same regular expression you provided, which is lacking if you properly want to recognize URLs with a regular expression. This one doesn't recognize https ones and the last dot in the following sentence would be a part of the URL: "This is a URL: http://www.google.com/."
What the code does is to split the text based on the regular expression, iterate it and adds the correct elements to the FlowDocument constructed on the fly.
Clicking the Hyperlink should open your default browser.
Result:
That said, this is only good for read only usage of the RichTextBox (as indicated by the question in the comment).
I want to use the exportWizard extension point for an eclipse plugin. I am having some difficulties figuring out how a simple filedialog wizard page should look like.
public class ExportWizardPage extends WizardPage {
private FileDialog fileDialog=null;
protected ExportWizardPage(String pageName) {
super(pageName);
}
#Override
public void createControl(Composite parent) {
fileDialog = new FileDialog(parent.getShell(), SWT.SAVE);
fileDialog.setFilterExtensions(new String[] { "*.bm" });
}
}
I am trying it currently like above and use a FileDialog for selecting the target file. Basically it works, the dialog is opened and I get the name of the file, but as soon the dialog closes I get an exception.
org.eclipse.core.runtime.AssertionFailedException: null argument:
at org.eclipse.core.runtime.Assert.isNotNull(Assert.java:85)
at org.eclipse.core.runtime.Assert.isNotNull(Assert.java:73)
at org.eclipse.jface.wizard.Wizard.createPageControls(Wizard.java:178)
I think I am using this Wizard/WizardPage mechanism wrongly, but I really could not found a simple example that showed me how something should look like.
Your wizard page does not contain any controls. You should create one composite and then add all your controls to it (and NOT parent directly). Calling setControl(..) is also absolutely required. It should look something like this:
#Override
public void createControl(Composite parent) {
Composite content = new Composite(parent, SWT.NONE);
// add all the controls to your wizard page here with 'content' as parent
FileDialog fileDialog = new FileDialog(parent.getShell(), SWT.SAVE);
fileDialog.setFilterExtensions(new String[] { "*.bm" });
setControl(content);
}
Sampling drag-and-drop between WinForm RichTextBoxes within one application and between them and external applications revealed two interesting observations (item 1 certainly appears to be a bug; in a perfect world, item 2 would probably be as well):
Some drag-and-drop operations delete the dragged text from the source container, whether or not it is set to read-only. (Thanks to Mark Morgan for first noticing this in his bug report on my open-source site.)
Whether text is retained or deleted from a source container is inconsistent among different applications.
I could not find any definitive reference indicating what drag-and-drop behavior is supposed to be. The closest I found was on page 476 of the Windows User Experience Interaction Guidelines (for Vista):
"Dragging and dropping: Object is moved or copied to the drop target". Well, that certainly aligns with my observations; some applications move the object, others copy it!
The questions: I would like to find a workaround for item 1 above; it irks me that a read-only container is not inviolate! As a secondary question, I am wondering if someone has a reference to how drag-and-drop is supposed to behave? When is it a move and when is it a copy?
My sample WinForm application (code below) contains two RichTextBox controls, the left one being read-only (call this RTB1) and initialized with some text; the right one (RTB2) being read/write so it may receive text. Both have drag-and-drop enabled for the test. Here are the combinations I tested; notice that in each grouping there is at least one "odd-man-out" :
From RTB1 to RTB2: move
From RTB1 to other RTB (external): move
From RTB1 to WordPad: copy
From RTB1 to Word2003: move
From RTB1 to Outlook2003: copy
From RTB1 to Firefox3.0: copy
From RTB2 to other RTB (external): move
From RTB2 to WordPad: copy
From RTB2 to Outlook2003: copy
From RTB2 to Firefox3.0: copy
From Outlook2003 to RTB2: move
From WordPad to RTB2: move
From Word2003 to RTB2: move
From other RTB (external) to RTB2: move
From Firefox3.0 to RTB2: copy
From Word2003 to Outlook2003: copy
From Outlook2003 to Word2003 : move
Tests run on WinXP.
Test app compiled with .NET 2.0 (tried a couple with .NET 3.5 with the same results).
Here is the sample application:
using System;
using System.Windows.Forms;
namespace RichTextBoxTest
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
partial class Form1 : Form
{
private RichTextBox richTextBox1 = new RichTextBox();
private RichTextBox richTextBox2 = new RichTextBox();
public Form1()
{
InitializeComponent();
}
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.SuspendLayout();
//
// richTextBox1
//
this.richTextBox1.EnableAutoDragDrop = true;
this.richTextBox1.Location = new System.Drawing.Point(34, 25);
this.richTextBox1.ReadOnly = true;
this.richTextBox1.Size = new System.Drawing.Size(122, 73);
this.richTextBox1.Text = "some stuff here";
//
// richTextBox2
//
this.richTextBox2.EnableAutoDragDrop = true;
this.richTextBox2.Location = new System.Drawing.Point(177, 25);
this.richTextBox2.Size = new System.Drawing.Size(122, 73);
this.richTextBox2.Text = "";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(338, 122);
this.Controls.Add(this.richTextBox2);
this.Controls.Add(this.richTextBox1);
this.Text = "Form1";
this.ResumeLayout(false);
}
}
}
Having had no tidbits submitted on this I delved further into the issue.
First, I obtained some information from Microsoft (via MSDN support) that standard drag-and-drop behavior does a move while holding down Control with drag-and-drop does a copy.
Next, consider these three modes of operation:
User can edit text.
User can move text (via drag-and-drop).
Application can change text programmatically.
According to Microsoft, setting read-only disables only item (1) ! To also honor read-only for item (2) one must manually code the solution rather than using the read-only property.
Well, to me this is clearly a defect. I believe read-only should disable both (1) and (2). So I submitted an official defect report to Microsoft Connect espousing this opinion. Alas, the response came back as essentially "Thanks, but not important enough issue to fix." Sigh...
Here's my scenario:
Shell with 1 TabControl and 1 region called MenuRegion
MenuRegion contains Buttons for each of the available modules (applications).
I want to achieve the following using Prism (Composite Application Library for WPF): When one of the buttons is clicked, I need to add a new TabItem to the TabControl, and load and individual instance of the corresponding module (application) inside this TabItem.
One module may appear several times in the TabControl.
I really appreciate your answer. But I don't believe you're using Prism (http://www.codeplex.com/CompositeWPF) are you? My question was more related to Prism, and I've edited it to be more clear now.
In Prism you dynamically load modules' views into regions. I am not sure how to do that in my scenario because the regions are to be set dynamically. How would I name them?
Thanks!
I'm new to this PRISM world (1 week experience :)) ) and had the same requirement!
First of all you have to get the Regionextensions from here.
The solution to my (may be your) problem is as follows:
have 2 regions (menu and tabcontrol - for mdi like behaviour)
tabitem header has to be prepared with a button for closing (which is bound to a command for closing this tabitem-actually hiding this tabitem)
send event from menu item to the module which should load the view (I've instantiated the modules on demand). In the module's initialize method subscribe to the event sent by the menu item. In the event handling method you simply re-show the tabitem
If this is to abstract to you I can send you a skeleton application I've developed to play around.
We do something similar, though we have the tab items already created (with no content) and show/hide as appropriate. When the tab item is selected, then we load the tab content.
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.OriginalSource != sender) return;
TabControl tabControl = (TabControl)sender;
TabItem tabItem = (TabItem)tabControl.SelectedItem;
if (!tabItem.HasContent)
AddTabContent(tabItem); // This will cause a refresh once the content is loaded.
else
Refresh(tabItem);
}
private void AddTabContent(TabItem tabItem)
{
IOptimusPage page = tabItem.Tag as IOptimusPage;
//This allows lazy loading of controls
if (page != null)
{
if (!tabItem.HasContent)
{
CustomerEngagementUserControl control = page.GetControl(DataContext as CustomerEngagementUIObject, Services);
tabItem.Content = control;
}
}
}
The tab item content is specified in the tab item tag, using pages which are responsible for creating the content.
<TabItem
Header="Personal Background"
Style="{StaticResource FirstBreadcrumbTabItem}"
x:Name="PersonalBackgroundTab">
<TabItem.Tag>
<Pages:FfnaPersonalBackgroundPage />
</TabItem.Tag>
</TabItem>
The page creates the control.
class FfnaPersonalBackgroundPage : IOptimusPage
{
#region IOptimusPage Members
public CustomerEngagementUserControl GetControl(CustomerEngagementUIObject dataContext, CustomerEngagementServices services)
{
CustomerEngagementUserControl control = new FfnaPersonalBackgroundControl();
control.DataContext = dataContext;
control.Services = services;
return control;
}
#endregion
}
You could use a similar technique to create your tab items on the fly.
I know it's quite late a response but I am doing something similar, although haven't achieved the full solution yet.
This code happens on the click event of a button which I am handling in the presenter. The module is defined in the config file.
ModuleInfo moduleInfoObject = this.moduleEnumerator.GetModule("ModuleA");
Assembly assembly = this.LoadAssembly(moduleInfoObject);
Type type = assembly.GetType(moduleInfoObject.ModuleType);
IModule aModule = this.CreateModule(type);
aModule.Initialize();
// - - - -Helper Methods - - - -
// - - - LoadAssembly - - -
private Assembly LoadAssembly(ModuleInfo moduleInfo)
{
string assemblyFile = moduleInfo.AssemblyFile;
assemblyFile = this.GetModulePath(assemblyFile);
FileInfo file = new FileInfo(assemblyFile);
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(file.FullName);
}
catch (Exception ex)
{
throw new ModuleLoadException(null, assemblyFile, ex.Message, ex);
}
return assembly;
} // LoadAssembly(moduleInfo)
// - - - CreateModule - - -
private IModule CreateModule(Type type)
{
return (IModule)containerFacade.Resolve(type);
} // CreateModule(type)
// - - - GetModulePath - - -
private string GetModulePath(string assemblyFile)
{
if (String.IsNullOrEmpty(assemblyFile))
{
throw new ArgumentNullException("assemblyFile");
} // if
if (Path.IsPathRooted(assemblyFile) == false)
{
assemblyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assemblyFile);
} // if
return assemblyFile;
} // GetModulePath(assemblyFile)
I am trying to unit test my WPF databindings using the test suit provided by Microsoft Team System. I would like to be able to test the bindings without showing the window because most of my tests will be for user controls and not actually on a window. Is this possible or is there a better way to do it? The code below works if I show the window, but if I don't, the bindings don't update.
Window1_Accessor target = new Window1_Accessor();
UnitTestingWPF.Window1_Accessor.Person p = new UnitTestingWPF.Window1_Accessor.Person() { FirstName = "Shane" };
Window1 window = (target.Target as Window1);
window.DataContext = p;
//window.Show(); //Only Works when I actually show the window
//Is it possible to manually update the binding here, maybe? Is there a better way?
Assert.AreEqual("Shane", target.textBoxFirstName.Text); //Fails if I don't Show() the window because the bindings aren't updated
While looking for a solution to convert WPF binding errors into exception, I figured out that it can also be used in a unit test project.
The technique is very simple:
Derive a TraceListener that throws instead of logging
Add that listener to PresentationTraceSources.DataBindingSource
Please see the complete solution on GitHub, it includes a unit test project.
Shane, if what you're really worried about is a binding breaking silently, you should look at redirecting the binding traces to somewhere you can examine. I'd start here:
http://blogs.msdn.com/mikehillberg/archive/2006/09/14/WpfTraceSources.aspx
Other than that, I agree with Gishu that bindings aren't good candidates for unit testing, mainly due to the automagic going on that Gishu mentioned in the "Epilogue". Instead focus on making sure the underlying class behaves correctly.
Note, too, that you can get even more robust traces using the PresentationTraceSources class:
http://msdn.microsoft.com/en-us/library/system.diagnostics.presentationtracesources.aspx
Hope that helps!
Eyeball it.
This kind of declarative markup rarely breaks.. unless someone goes in manual and screws it up. Even then, you can fix it within minutes. IMHO the cost of writing such tests far outweigh the benefits.
Update[Dec3,08]: Alrighty then.
The test is just testing that the textbox has the value "FirstName" as the Path property of the binding. If I change/refactor FirstName to JustName in the actual data source object, the test would still pass since it is testing against an anonymous type. (Green test when code broken - TDD Antipattern: The Liar)
If your aim is to verify that FirstName has been specified in XAML,
Assert.AreEqual("FirstName", txtBoxToProbe.GetBindingExpression(TextBox.TextProperty).ParentBinding.Path.Path);
If you really must catch broken bindings via unit tests (and don't want to show the UI), use the real data source... struggled for a while and came up with this.
[Test]
public void TestTextBoxBinding()
{
MyWindow w = new MyWindow();
TextBox txtBoxToProbe = w.TextBox1;
Object obDataSource = w; // use 'real' data source
BindingExpression bindingExpr = BindingOperations.GetBindingExpression(txtBoxToProbe, TextBox.TextProperty);
Binding newBind = new Binding(bindingExpr.ParentBinding.Path.Path);
newBind.Source = obDataSource;
txtBoxToProbe.SetBinding(TextBox.TextProperty, newBind);
Assert.AreEqual("Go ahead. Change my value.", txtBoxToProbe.Text);
}
Epilogue:
There's some real covert stuff happening in the call to Window.Show(). It somehow magically sets up the DataItem property after which data binding starts working.
// before show
bindingExpr.DataItem => null
bindingExpr.Status => BindingStatus.Unattached
// after show
bindingExpr.DataItem => {Actual Data Source}
bindingExpr.Status => BindingStatus.Active
Once the Binding is Active, I guess you can force textbox updates via code like this..
txtBoxToProbe.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
Once again, I voice my reluctance against this approach. Getting NUnit to run in STA was a pain..
Combining advice I came across in a number of SO posts I wrote the following class which works very well to test WPF bindings.
public static class WpfBindingTester
{
/// <summary>load a view in a hidden window and monitor it for binding errors</summary>
/// <param name="view">a data-bound view to load and monitor for binding errors</param>
public static void AssertBindings(object view)
{
using (InternalTraceListener listener = new InternalTraceListener())
{
ManualResetEventSlim mre = new ManualResetEventSlim(false);
Window window = new Window
{
Width = 0,
Height = 0,
WindowStyle = WindowStyle.None,
ShowInTaskbar = false,
ShowActivated = false,
Content = view
};
window.Loaded += (_, __) => mre.Set();
window.Show();
mre.Wait();
window.Close();
Assert.That(listener.ErrorMessages, Is.Empty, listener.ErrorMessages);
}
}
/// <summary>Is the test running in an interactive session. Use with Assume.That(WpfBindingTester.IsAvailable) to make sure tests only run where they're able to</summary>
public static bool IsAvailable { get { return Environment.UserInteractive && Process.GetCurrentProcess().SessionId != 0; } }
private class InternalTraceListener : TraceListener
{
private readonly StringBuilder _errors = new StringBuilder();
private readonly SourceLevels _originalLevel;
public string ErrorMessages { get { return _errors.ToString(); } }
static InternalTraceListener() { PresentationTraceSources.Refresh(); }
public InternalTraceListener()
{
_originalLevel = PresentationTraceSources.DataBindingSource.Switch.Level;
PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Error;
PresentationTraceSources.DataBindingSource.Listeners.Add(this);
}
public override void Write(string message) {}
public override void WriteLine(string message) { _errors.AppendLine(message); }
protected override void Dispose(bool disposing)
{
PresentationTraceSources.DataBindingSource.Listeners.Remove(this);
PresentationTraceSources.DataBindingSource.Switch.Level = _originalLevel;
base.Dispose(disposing);
}
}
}
you can try Guia.
With it you can unit-test your UserControl and check if the data binding is correct. You have to show the window though.
Here is an example. It starts a new instance of your UserControl and sets its DataContext and then checks if the textbox is set to the right value.
[TestMethod]
public void SimpleTest()
{
var viewModel = new SimpleControlViewModel() {TextBoxText = "Some Text"};
customControl = CustomControl.Start<SimpleUserControl>((control) => control.DataContext = viewModel);
Assert.AreEqual("Some Text", customControl.Get<TextBox>("textbox1").Value);
customControl.Stop();
}