Attached behavior not working in reuseable window - wpf

I have a dialog that use, that i simple can't understand why it will not bind.
I use Joe White's DialogCloser, from this answer
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(DialogCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null)
window.DialogResult = e.NewValue as bool?;
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
I use it only in DialogWindow.xml
<Window x:Class="ATS.Views.Common.DialogWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="WindowDialog"
WindowStyle="SingleBorderWindow"
WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight"
xmlns:xc="clr-namespace:ATS.Views.Common"
xc:DialogCloser.DialogResult="{Binding DialogResult}"
ResizeMode="NoResize">
<ContentPresenter x:Name="DialogPresenter" Content="{Binding .}"></ContentPresenter>
The Binding it self is on a abstract class that I want to use for dialog windows.
public class DialogViewModel : BaseViewModel
{
private bool? dialogResult;
public bool? DialogResult
{
get
{
return dialogResult;
}
set
{
dialogResult = value;
NotifyPropertyChange("DialogResult");
}
}
}
I open dialogs from ViewModels with this:
public bool? ShowDialog(string title, DialogViewModel dataContext)
{
var win = new DialogWindow();
win.Title = title;
win.DataContext = dataContext;
return win.ShowDialog();
}
Now what seems to happen, even tho i create new dialog windows, is that the binding it self only get added once, and therefor it will only work on the first dialog that is created.
Is it something to do with binding to an abstact class that makes the bindings not work, due to same namespace and names?
Edit:
Ended up using Mark's way of handling dialog boxes, which can be found here

Related

Closing Child Window from ViewModel

I have Cancel Command in ViewModel.
Which is bound to a cannel Button in child View.
When i press cancel button it will clear all my unsaved data in viewModel.
Additionally i have to close the current instance of child window.-This is where I am stuck.
I am using MVVM.
I use the following pattern.
I have a base class for my ViewModel
public abstract class ClosableViewModel : IClosableViewModel
{
public event EventHandler Close;
protected virtual void CloseView()
{
var handler = Close;
if (handler != null) handler(this, EventArgs.Empty);
}
}
which is implementing this interface
public interface IClosableViewModel
{
event EventHandler Close;
}
And a window base class for my View I want to show and close via a ViewModel
public class ClosableWindow : Window
{
public ClosableWindow(IClosableViewModel viewModel)
{
DataContext = viewModel;
viewModel.Close += (s, e) => Close();
}
}
Your ViewModel which is the DataContext from your View you want to show as dialog has to inherit from ClosableViewModel and your dialog has to inherit from ClosableWindow. When you want to close your View from the ViewModel you just have to call the CloseView method.
An alternative to using an event is an attached property that goes on the view. The property changed handler will find the parent window of the view and close it as soon as a particular value is recognized.
using System.Windows;
namespace WpfApp1
{
public class CloseSignal
{
public static readonly DependencyProperty SignalProperty =
DependencyProperty.RegisterAttached("Signal", typeof(bool), typeof(CloseSignal),
new PropertyMetadata(OnSignalChanged));
public static bool GetSignal(DependencyObject dp)
{
return (bool)dp.GetValue(SignalProperty);
}
public static void SetSignal(DependencyObject dp, bool value)
{
dp.SetValue(SignalProperty, value);
}
private static void OnSignalChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
if (!(bool)e.NewValue)
return;
Window parent = Window.GetWindow(dp);
if (parent != null)
parent.Close();
}
}
}
And the view's XAML looks something like...
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
Title="MainWindow" Height="350" Width="525"
local:CloseSignal.Signal="{Binding Signal}">
...
</Window>

Unable to close modal dialog with attached property (MVVM)?

A newbie question, but I can't get it to work. In trying to use the answer to SO "How Should the View Model Close the form"
, I have a UserControl defined with:
<UserControl
.....
h:DialogCloser.DialogResult="{Binding DialogResult}"
>
which while designing shows:
Property 'DialogResult' is not attachable to elements of type 'UserControl'.
The DialogCloser is defined as:
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(DialogCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null)
window.DialogResult = e.NewValue as bool?;
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
The UserControl is opened by:
var win = new WindowDialog();
win.Title = title;
win.DataContext = datacontext;
return win.ShowDialog();
In the View Model for the UserControl, I have:
public new void DoExit()
{
DialogResult = true;
}
private bool? dialogresult;
public bool? DialogResult
{
get
{
return dialogresult;
}
set
{
if (dialogresult != value)
{
dialogresult = value;
OnPropertyChanged("DialogResult");
}
}
}
When using DoExit(), the modal dialog, my UserControl, does not close. What's wrong? And how do I get the designer (VS 2010) to not throw error?
Thanks is advance for any help.
Addendum:
public class ModalDialogService : IModalDialogService
{
public bool? ShowDialog(string title, object datacontext)
{
var win = new WindowDialog();
win.Title = title;
win.DataContext = datacontext;
return win.ShowDialog();
}
}
Note: If the "UserControl" is rather made as a "Window", the designer is happy, but then the error is:
"Window must be the root of the tree. Cannot add Window as a child of Visual."
Help somebody?
I am new to this, but putting everything together, I would suggest this as a possible answer for newbies like myself.
Does this comply with best MVVM practices?
First, as the designer stated "Property 'DialogResult' is not attachable to elements of type 'UserControl'.
My translation, a "control" is not a window--so it can't be closed. Therefore,
h:DialogCloser.DialogResult="{Binding DialogResult}"
is wrong. Remove it.
Second, my usercontrol is being displayed as the sole element of an otherwise plain window as
<Window x:Class="Nova5.UI.Views.Ink.WindowDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowDialog"
WindowStyle="SingleBorderWindow"
WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">
<ContentPresenter x:Name="DialogPresenter" Content="{Binding .}"/>
</Window>
Now, the whole thing is initiated when the top most view model does
var dialog = new WindowDialog
{
Title = "It's me Margaret",
ShowInTaskbar = false,
Topmost = true,
ResizeMode = ResizeMode.NoResize
};
dialog.Owner = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
ModalDialogService dialogService = new ModalDialogService();
dialogService.ShowDialog<PrescriptionWriterViewModel>(dialog,
new PrescriptionWriterViewModel(),
returnedViewModelInstance =>
{
if (dialog.DialogResult.HasValue && dialog.DialogResult.Value)
{
}
}
);
};
When Exit is clicked on the usercontrol, it performs by way of a DelegateCommand in its view model
public new void DoExit()
{
OnRequestClose();
}
public event EventHandler RequestClose;
void OnRequestClose()
{
EventHandler handler = this.RequestClose;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
This request to close is then handled in the initiating dialog service,
public void ShowDialog<TDialogViewModel>(IModalWindow view, TDialogViewModel viewModel, Action<TDialogViewModel> onDialogClose)
{
view.DataContext = viewModel;
IDialogViewModel vm = viewModel as IDialogViewModel;
if (vm !=null)
vm.RequestClose += (s,e)=> view.Close(); //RequestClose is the event in the ViewModel that the view will listen for.
if (onDialogClose != null)
{
view.Closed += (sender, e) => onDialogClose(viewModel);
}
view.Show();
}
When the view is closed, the onDialogClose(viewModel), then calls back to the topmost view model and completes the action specified with
returnedViewModelInstance =>
{
if (dialog.DialogResult.HasValue && dialog.DialogResult.Value)
{
}
}
So completes several intense hours of learning. I hope it is of use to somebody.

Binding text to attached property

My question is similar to this: WPF Generate TextBlock Inlines but I don't have enough reputation to comment. Here is the attached property class:
public class Attached
{
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(TextBlock),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure));
public static void SetFormattedText(DependencyObject textBlock, string value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
public static string GetFormattedText(DependencyObject textBlock)
{
return (string)textBlock.GetValue(FormattedTextProperty);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as TextBlock;
if (textBlock == null)
{
return;
}
var formattedText = (string)e.NewValue ?? string.Empty;
formattedText = string.Format("<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{0}</Span>", formattedText);
textBlock.Inlines.Clear();
using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
{
var result = (Span)XamlReader.Load(xmlReader);
textBlock.Inlines.Add(result);
}
}
}
I'm using this attached property class and trying to apply it to a textblock to make the text recognize inline values like bold, underline, etc from a string in my view model class. I have the following XAML in my textblock:
<TextBlock Grid.Row="1" Grid.Column="1" TextWrapping="Wrap" my:Attached.FormattedText="test" />
However I get nothing at all in the textblock when I start the program. I also would like to bind the text to a property on my view model eventually but wanted to get something to show up first...
Sorry this is probably a newbie question but I can't figure out why it's not working. It doesn't give me any error here, just doesn't show up. If I try to bind, it gives me the error:
{"A 'Binding' cannot be set on the 'SetFormattedText' property of type 'TextBlock'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject."}
First, the type of property needs to be a class name, not the type TextBlock:
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(TextBlock), <----- Here
Second, the handler is not called, it must be registered here:
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure,
YOUR_PropertyChanged_HANDLER)
Thirdly, an example to work, you need to specify the input string like this:
<Bold>My little text</Bold>
Working example is below:
XAML
<Window x:Class="InlineTextBlockHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:InlineTextBlockHelp"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock Name="TestText"
this:AttachedPropertyTest.FormattedText="TestString"
Width="200"
Height="100"
TextWrapping="Wrap" />
<Button Name="TestButton"
Width="100"
Height="30"
VerticalAlignment="Top"
Content="TestClick"
Click="Button_Click" />
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
string inlineExpression = "<Bold>Once I saw a little bird, go hop, hop, hop.</Bold>";
AttachedPropertyTest.SetFormattedText(TestText, inlineExpression);
}
}
public class AttachedPropertyTest
{
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(AttachedPropertyTest),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure, FormattedTextPropertyChanged));
public static void SetFormattedText(DependencyObject textBlock, string value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
public static string GetFormattedText(DependencyObject textBlock)
{
return (string)textBlock.GetValue(FormattedTextProperty);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as TextBlock;
if (textBlock == null)
{
return;
}
var formattedText = (string)e.NewValue ?? string.Empty;
formattedText = string.Format("<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{0}</Span>", formattedText);
textBlock.Inlines.Clear();
using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
{
var result = (Span)XamlReader.Load(xmlReader);
textBlock.Inlines.Add(result);
}
}
}
Initially be plain text, after clicking on the Button will be assigned to inline text.
Example for MVVM version
To use this example in MVVM style, you need to create the appropriate property in the Model/ViewModel and associate it with the attached dependency property like this:
<TextBlock Name="TestText"
PropertiesExtension:TextBlockExt.FormattedText="{Binding Path=InlineText,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Width="200"
Height="100"
TextWrapping="Wrap" />
Property in Model/ViewModel must support method NotifyPropertyChanged.
Here is a full sample:
AttachedProperty
public class TextBlockExt
{
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(TextBlockExt),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure, FormattedTextPropertyChanged));
public static void SetFormattedText(DependencyObject textBlock, string value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
public static string GetFormattedText(DependencyObject textBlock)
{
return (string)textBlock.GetValue(FormattedTextProperty);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as TextBlock;
if (textBlock == null)
{
return;
}
var formattedText = (string)e.NewValue ?? string.Empty;
formattedText = string.Format("<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{0}</Span>", formattedText);
textBlock.Inlines.Clear();
using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
{
var result = (Span)XamlReader.Load(xmlReader);
textBlock.Inlines.Add(result);
}
}
}
MainViewModel
public class MainViewModel : NotificationObject
{
private string _inlineText = "";
public string InlineText
{
get
{
return _inlineText;
}
set
{
_inlineText = value;
NotifyPropertyChanged("InlineText");
}
}
}
MainWindow.xaml
<Window x:Class="InlineTextBlockHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:InlineTextBlockHelp.ViewModels"
xmlns:PropertiesExtension="clr-namespace:InlineTextBlockHelp.PropertiesExtension"
Title="MainWindow" Height="350" Width="525"
ContentRendered="Window_ContentRendered">
<Window.DataContext>
<ViewModels:MainViewModel />
</Window.DataContext>
<Grid>
<TextBlock Name="TestText"
PropertiesExtension:TextBlockExt.FormattedText="{Binding Path=InlineText,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Width="200"
Height="100"
TextWrapping="Wrap" />
</Grid>
</Window>
Code-behind (just for test)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_ContentRendered(object sender, EventArgs e)
{
MainViewModel mainViewModel = this.DataContext as MainViewModel;
mainViewModel.InlineText = "<Bold>Once I saw a little bird, go hop, hop, hop.</Bold>";
}
}
This example is available at this link.

Binding Data to a UserControl hosted by a DataTemplate

I have a user control which exposes a property which is a long. I'd like to instantiate this control and bind to the exposed property in a data template.
I'm seeing xaml errors in the resource file. The ambiguous "must have derivative of panel as the root element". And when I run this in a debugger, I see that the value of TeamIdx is -1 and is not being set.
<DataTemplate x:Key="TeamScheduleTemplate">
<Grid HorizontalAlignment="Left" Width="400" Height="600">
<Team:ScheduleControl TeamIdx="{Binding Idx}" />
</Grid>
</DataTemplate>
public sealed partial class ScheduleControl : UserControl
{
public static readonly DependencyProperty TeamIdxProperty =
DependencyProperty.Register(
"TeamIdx",
typeof(long),
typeof(ScheduleControl),
new PropertyMetadata((long)-1));
public long TeamIdx
{
get { return (long)GetValue(TeamIdxProperty); }
set { SetValue(TeamIdxProperty, value); }
}
public ScheduleControl()
{
this.InitializeComponent();
var team = TeamLookup.GetTeam(TeamIdx);
}
}
Edit: It turns out that the binding doesn't happen until after the control is constructed. In retrospect, this makes total sense. The solution I used is below:
public sealed partial class ScheduleControl : UserControl
{
public static readonly DependencyProperty TeamIdxProperty =
DependencyProperty.Register(
"TeamIdx",
typeof(long),
typeof(ScheduleControl),
new PropertyMetadata(
(long)-1,
OnTeamIdxChanged));
public long TeamIdx
{
get { return (long)GetValue(TeamIdxProperty); }
set { SetValue(TeamIdxProperty, value); }
}
private static void OnTeamIdxChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var target = (ScheduleControl)sender;
target.OnTeamIdxChanged((long)e.NewValue);
}
private void OnTeamIdxChanged(long id)
{
var model = FindModel(id);
this.DataContext = model;
}
public ScheduleControl()
{
this.InitializeComponent();
}
}

WPF CommandParameter Binding Problem

I'm having some trouble understanding how command parameter binding works.
When I create an instance of the widget class before the call to InitializeComponent it seems to work fine. Modifications to the parameter(Widget) in the ExecuteCommand function will be "applied" to _widget. This is the behavior I expected.
If the instance of _widget is created after InitializeComponent, I get null reference exceptions for e.Parameter in the ExecuteCommand function.
Why is this? How do I make this work with MVP pattern, where the bound object may get created after the view is created?
public partial class WidgetView : Window
{
RoutedCommand _doSomethingCommand = new RoutedCommand();
Widget _widget;
public WidgetView()
{
_widget = new Widget();
InitializeComponent();
this.CommandBindings.Add(new CommandBinding(DoSomethingCommand, ExecuteCommand, CanExecuteCommand));
}
public Widget TestWidget
{
get { return _widget; }
set { _widget = value; }
}
public RoutedCommand DoSomethingCommand
{
get { return _doSomethingCommand; }
}
private static void CanExecuteCommand(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Parameter == null)
e.CanExecute = true;
else
{
e.CanExecute = ((Widget)e.Parameter).Count < 2;
}
}
private static void ExecuteCommand(object sender, ExecutedRoutedEventArgs e)
{
((Widget)e.Parameter).DoSomething();
}
}
<Window x:Class="CommandParameterTest.WidgetView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WidgetView" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<Button Name="_Button" Command="{Binding DoSomethingCommand}"
CommandParameter="{Binding TestWidget}">Do Something</Button>
</StackPanel>
</Window>
public class Widget
{
public int Count = 0;
public void DoSomething()
{
Count++;
}
}
InitializeCompenent processes the xaml associated with the file. It is at this point in time that the CommandParameter binding is first processed. If you initialize your field before InitializeCompenent then your property will not be null. If you create it after then it is null.
If you want to create the widget after InitializeCompenent then you will need to use a dependency property. The dependency proeprty will raise a notification that will cause the CommandParameter to be updated and thus it will not be null.
Here is a sample of how to make TestWidget a dependency property.
public static readonly DependencyProperty TestWidgetProperty =
DependencyProperty.Register("TestWidget", typeof(Widget), typeof(Window1), new UIPropertyMetadata(null));
public Widget TestWidget
{
get { return (Widget) GetValue(TestWidgetProperty); }
set { SetValue(TestWidgetProperty, value); }
}
Even with the dependency property, you still need to call CommandManager.InvalidateRequerySuggested to force the CanExecute of the Command being evaluated.

Resources