I'm trying to create a dialog class in WPF. This class inherits from Window and provides some default buttons and settings.
The implementation basically looks like this:
namespace Commons {
public class Dialog : Window {
public new UIElement Content {
get { return this.m_mainContent.Child; }
set { this.m_mainContent.Child = value; }
}
// The dialog's content goes into this element.
private readonly Decorator m_mainContent = new Decorator();
// Some other controls beside "m_mainContent".
private readonly StackPanel m_buttonPanel = new StackPanel();
public Dialog() {
DockPanel content = new DockPanel();
DockPanel.SetDock(this.m_buttonPanel, Dock.Bottom);
content.Children.Add(this.m_buttonPanel);
content.Children.Add(this.m_mainContent);
base.Content = content;
}
public void AddButton(Button button) {
...
}
}
}
As you can see, I redefined the Content property.
Now I want to be able to use this dialog class in XAML like this:
<my:Dialog x:Class="MyDialogTest.TestDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:Commons;assembly=Commons"
Title="Outline" Height="800" Width="800">
<!-- Dialog contents here -->
</my:Dialog>
However, this will use Window.Content when setting the dialog's contents rather than Dialog.Content. How do I make this work?
You might need to indicate a property in "your" class as being the "content property" so that the child elements described by the XAML "content" of your Dialog get put into it instead of in the "content" property of your base Window.
[ContentProperty("Content")]
public class Dialog : Window {
If that doesn't work, then try changing the name.....so try this:
[ContentProperty("DialogContent")]
public class Dialog : Window {
public new UIElement DialogContent {
get { return this.m_mainContent.Child; }
set { this.m_mainContent.Child = value; }
}
Related
I have an app using Caliburn.micro and a custom window manager. The window manager is creating my own Base Window so I can customize the look and feel across the application.
I would like to add some controls on the window something like:
<DockPanel>
<ContentPresenter Content="{Binding CustomContent}" />
<StatusBar Height="20" DockPanel.Dock="Bottom" Background="Blue"/>
</DockPanel>
I would like to have Caliburn put the usercontrol from my ViewModel in the ContentPresenter, but Caliburn is replacing the entire content of my window.
I did this in the window:
using System.Windows;
namespace CaliburnCustomWindow
{
public partial class WindowBase
{
public static readonly DependencyProperty CustomContentProperty = DependencyProperty.Register("CustomContent", typeof (object), typeof (WindowBase));
public object CustomContent
{
get { return GetValue(CustomContentProperty); }
set { SetValue(CustomContentProperty, value); }
}
public WindowBase()
{
InitializeComponent();
}
}
}
And then modified my WindowManager to do this:
using System.Windows;
using Caliburn.Micro;
namespace CaliburnCustomWindow
{
internal class AppWindowManager : WindowManager
{
protected override Window EnsureWindow(object model, object view, bool isDialog)
{
Window window = view as Window;
if (window == null)
{
if (view.GetType() == typeof (MainView))
{
window = new WindowBase
{
CustomContent = view,
SizeToContent = SizeToContent.Manual
};
window.Height = 500;
window.Width = 500;
}
window.SetValue(View.IsGeneratedProperty, true);
}
else
{
Window owner2 = InferOwnerOf(window);
if (owner2 != null && isDialog)
{
window.Owner = owner2;
}
}
return window;
}
}
}
But it doesn't work. That binding to the CustomContent dependency property doesn't seem to work.
Is is possible to do this? If so how?
Could you not either use the default WindowManager implementation, and pass in a new instance of a wrapper DialogViewModel (and create the associated DialogView):
this.WindowManager.ShowDialog(new DialogViewModel(myViewModel));
or abstract this code in an implementation of an IDialogPresenter or similar if you wanted to simplify the client code:
this.DialogPresenter.Show(myViewModel);
I have a custom class:
class CustomClass
{
public string Test { get; set; }
CustomClass()
{
this.Test = "";
}
}
I'm declaring this custom class on a Application.Resources like that:
<Application.Resources>
<local:CustomClass x:Key="myobj"/>
</Application.Resources>
This resource is the DataContext of a grid and the TextBox binds the Test property, like that:
<Grid DataContext="{DynamicResource myobj}">
<TextBox Text="{Binding Path=Test, Mode=TwoWay}"></TextBox>
</Grid>
Suddenly at run-time, I change the value of the resource
this.Resources["myobj"] = new CustomClass() { Test = "12456" };
I want the value referenced on TextBox be always the value of the object that is currently on "myobj" resource, and I want change automatically the value of the current object when the value of Text property of the TextBox is changed, because of this, I used the Mode=TwoWay, but it's not happening.
I used WPF Inspector and I saw when the resource value is changed, binds a new cleared object and not my created object
I'm new in WPF sorry my english and my unknowledge;
Regards,
EDIT 1
It works implementing the code posted by ethicallogics, thanks! But sorry if I wasn't clear before, when binds a new resource as below
this.Resources["myobj"] = new instance;
it works fine when it is called inside the same window that this resource was declared, unlike when I call this line inside a UserControl, it seems that the UserControl doesn't inherit the MainWindow Resources, how that really works ?
class CustomClass:INotifyPropertyChanged
{
private string _test;
public string Test
{
get
{
return _test;
}
set
{
_test = value;
Notify("Test");
}
}
CustomClass()
{
this.Test = "";
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void Notify(string propName)
{
if(PropertyChanged!=null)
PropertyChanged(this,new PropertyChangedEventArgs(propName);
}
}
Use this class .I hope this will help.
I have a UserControl named myControl and there is a 3 columns Grid within it.
<Grid Name="main">
<Grid Grid.Column="0"/><Grid Grid.Column="1"/><Grid Grid.Column="2"/>
</Grid>
The client can use it in this way and it is ok.
<myControl />
My problem is, the client want to add an Element into the first column of "main" Grid, like:
<myControl>
<TextBlock Text="abc"/>
</myControl>
In this case, the TextBlock will replace the originated Content, here it is the "main" Grid.
What should I do to support the additional element? Great thanks.
You can use something like following:
// This allows "UserContent" property to be set when no property is specified
// Example: <UserControl1><TextBlock>Some Text</TextBlock></UserControl1>
// TextBlock goes into "UserContent"
[ContentProperty("UserContent")]
public partial class UserControl1 : UserControl
{
// Stores default content
private Object defaultContent;
// Used to store content supplied by user
private Object _userContent;
public Object UserContent
{
get { return _userContent; }
set
{
_userContent = value;
UpdateUserContent();
}
}
private void UpdateUserContent()
{
// If defaultContent is not set, backup the default content into it
// (will be set the very first time this method is called)
if (defaultContent == null)
{
defaultContent = Content;
}
// If there is something in UserContent, set it to Content
if (UserContent != null)
{
Content = UserContent;
}
else // Otherwise load default content back
{
Content = defaultContent;
}
}
public UserControl1()
{
InitializeComponent();
}
}
I have two Prism modules.
I want one of them register a window and the other one show this window using the "Show Dialog" mode.
How can it be done (if it can be done)?
Yes, it can be done. This is rough procedure:
Declare interface for this View in your "Infrastructure" project
public interface IMyDialogWindow
{
}
[Export] class that implements this interface in your module
[Export(typeof(IMyDialogWindow))]
public class MyClassInModuleA : IMyDialogWindow
{
}
[Import] this class in other module and use it for Dialog
[Import]
public IMyDialogWindow PropertyInModuleB
Well. I think I solved it by following this tip. But I don't know if it was the best solution.
I just created a window on my Shell project. This window is the one that will be popped up as a dialog window.
Here is its code:
Popup.xaml:
<Window x:Class="TryERP2.Shell.Views.Popup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Popup" Height="315" Width="411"
xmlns:prism="http://www.codeplex.com/prism">
<Grid>
<ContentControl x:Name="DialogRegion" Grid.Row="1" prism:RegionManager.RegionName="DialogRegion" />
</Grid>
</Window>
Popup.xaml.cs:
public partial class Popup : Window
{
private static Popup popup;
private Popup(IRegionManager regionManager)
{
InitializeComponent();
RegionManager.SetRegionManager(this, regionManager);
}
//Using the singleton pattern
public static Popup getPopup(IRegionManager regionManager)
{
if (popup == null)
popup = new Popup(regionManager);
return popup;
}
}
And, finally, when I want to show the dialog (in a Command which is in a module), I just instantiate it and inform what's the RegionManager:
private void showDialog()
{
// Acquiring the RegionManager
var regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
// Getting the Popup object
Popup p = Popup.getPopup(regionManager);
// Looking for the view I want to show in the dialog
var x = new Uri("MyView", UriKind.Relative);
// Changing the view of the DialogRegion (which is within the Popup)
regionManager.RequestNavigate("DialogRegion", x);
// Showing the dialog
p.ShowDialog();
}
I have a listbox defined in XAML as:
<ListBox x:Name="directoryList"
MinHeight="100"
Grid.Row="0"
ItemsSource="{Binding Path=SelectedDirectories}"/>
The SelectedDirectories is a property on the lists DataContext of type List<DirectoryInfo>
The class which is the datacontext for the listbox implements INotifyPropertyChanged. When the collection changes the items are added successfully to the list however the display does not update until I force the listbox to redraw by resizing it.
Any ideas why?
EDIT: INotifyPropertyChanged implementation
public class FileScannerPresenter : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private FileScanner _FileScanner;
public FileScannerPresenter()
{
this._FileScanner = new FileScanner();
}
public List<DirectoryInfo> SelectedDirectories
{
get
{
return _FileScanner.Directories;
}
}
public void AddDirectory(string path)
{
this._FileScanner.AddDirectory(path);
OnPropertyChanged("SelectedDirectories");
}
public void OnPropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Try
ObservableCollection<DirectoryInfo>
instead - you're triggering a refresh of the entire ListBox for no reason, and you don't need to make your hosting class implement INotifyPropertyChanged - it could easily just be a property of the window. The key is to never set the property to a new instance. So:
class SomeWindow : Window {
public ObservableCollection<DirectoryInfo> SelectedDirectories {get; private set;}
SomeWindow() { SelectedDirectories = new ObservableCollection<DirectoryInfo>(); }
public void AddDirectory(string path) {
SelectedDirectories.Add(new DirectoryInfo(path));
}
}
If you end up using that FileScanner class, you need to implement INotifyCollectionChanged instead - that way, the ListBox knows what to add/remove dynamically.
(See Update below). WPF seems to be working alright. I put your code into a new project. The listbox updates whenever I click the button to invoke AddDirectory. You should not need any more code changes.
The problem seems to be something else.. Are there multiple threads in your UI?
I didnt have the FileScanner type. So I created a dummy as follows.
public class FileScanner
{
string _path;
public FileScanner()
{ _path = #"c:\"; }
public List<DirectoryInfo> Directories
{
get
{
return Directory.GetDirectories(_path).Select(path => new DirectoryInfo(path)).ToList();
}
}
internal void AddDirectory(string path)
{ _path = path; }
}
No changes to your FileScannerPresenter class. Or your listbox XAML. I created a Window with a DockPanel containing your listbox, a textbox and a button.
Update: Paul Betts is right. It works because I return a new list each time from the Bound property. Data binding with lists always messes me up.
With more tinkering, the easy way to do this is:
Make FileScanner#Directories return an ObservableCollection<DirectoryInfo> (which implements INotifyCollectionChanged for you). Change all signatures all the way up to return this type instead of a List<DirectoryInfo>
FileScanner and FileScannerPresenter themselves do not have to implement any INotifyXXX interface.
// in FileScanner class def
public ObservableCollection<DirectoryInfo> Directories
{
get
{ return _DirList; }
}
internal void AddDirectory(string path)
{
_path = path;
//var newItems = Directory.GetDirectories(_path).Select(thePath => new DirectoryInfo(thePath)).ToList();
//_DirList.Concat( newItems ); -- doesn't work for some reason.
foreach (var info in Directory.GetDirectories(_path).Select(thePath => new DirectoryInfo(thePath)).ToList())
{
_DirList.Add(info);
}
}