I'm new at Silverlight.
I've created a sort of master page using a Page with a frame where the content is loaded. As I handle multiple UserControls at the time (only one is shown, but I want to keep the state of the opened before) I'm setting Content property instead of Navigate method. That way I can assign a UserControl (already created, not a new one as it would be using Navigate with the Uri to the UserControl).
Now I want to take a picture as shown here from the frame when its content changes. If I do it immediately when the content set, the UserControl won't be shown in the picture because it takes a few secs. Frames have the event Navigated, but it doesn't fire with property Content (it just fires when the method Navigate is used, as it name says).
How can I know when new Content is loaded?
If it helps I'm using Silverligh 5.
I've a solution but I don't really like it, so I'm still looking for other ways.
public class CustomFrame : Frame
{
private readonly RoutedEventHandler loadedDelegate;
public static readonly DependencyProperty UseContentInsteadNavigationProperty =
DependencyProperty.Register("UseContentInsteadNavigation", typeof (bool), typeof (CustomFrame), new PropertyMetadata(true));
public bool UseContentInsteadNavigation
{
get { return (bool)GetValue(UseContentInsteadNavigationProperty); }
set { SetValue(UseContentInsteadNavigationProperty, value); }
}
public CustomFrame()
{
this.loadedDelegate = this.uc_Loaded;
}
public new object Content
{
get { return base.Content; }
set
{
if (UseContentInsteadNavigation)
{
FrameworkElement fe = (FrameworkElement)value;
fe.Loaded += loadedDelegate;
base.Content = fe;
}
else
{
base.Content = value;
}
}
}
void uc_Loaded(object sender, RoutedEventArgs e)
{
((UserControl)sender).Loaded -= loadedDelegate;
OnContentLoaded();
}
public delegate void ContentLoadedDelegate(Frame sender, EventArgs e);
public event ContentLoadedDelegate ContentLoaded;
private void OnContentLoaded()
{
if (ContentLoaded != null)
ContentLoaded(this, new EventArgs());
}
}
Related
I want to find a way to implement different forms of modals in MVVM WPF application. Like dialogs with returning results, message boxes or modal sub-windows with some controls inside.
Can you give me an advice about an efficient and modern approach for it?
I rarely find much use for anything other than a confirmation request. "Do you really want to delete that?" kind of thing.
Things popping up and asking for extra input just aren't super useful in my experience.
To my mind though, what you're doing is splitting your code. There is code up to showing the dialog. There is then code happens if the user clicks OK or Yes or selects a thingummajig in the dialog.
I split these into separate pieces of code. So there is not necessarilly a need to stop code running. It's in a separate method ( or command ) which is only run if the user hits the right button.
My first approach uses a control which itself has no UI. It exists just to get something encapsulated into the view.
namespace UserInput
{
public class ConfirmationRequestor : Control, ICommandSource
{
public bool? ShowConfirmDialog
{
get
{
return (bool?)GetValue(ShowConfirmDialogProperty);
}
set
{
SetValue(ShowConfirmDialogProperty, value);
}
}
public static readonly DependencyProperty ShowConfirmDialogProperty =
DependencyProperty.Register("ShowConfirmDialog",
typeof(bool?),
typeof(ConfirmationRequestor),
new FrameworkPropertyMetadata(null
, new PropertyChangedCallback(ConfirmDialogChanged)
)
{ BindsTwoWayByDefault = true }
);
private static void ConfirmDialogChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool?)e.NewValue != true)
{
return;
}
ConfirmationRequestor cr = (ConfirmationRequestor)d;
Window parent = Window.GetWindow(cr) as Window;
MessageBoxResult result = MessageBox.Show(parent, cr.Message, cr.Caption, cr.MsgBoxButton, cr.MsgBoxImage);
if (result == MessageBoxResult.OK || result == MessageBoxResult.Yes)
{
if (cr.Command != null)
{
cr.Command.Execute(cr.CommandParameter);
}
}
cr.SetValue(ShowConfirmDialogProperty, false);
}
public MessageBoxButton MsgBoxButton
{
get { return (MessageBoxButton)GetValue(MsgBoxButtonProperty); }
set { SetValue(MsgBoxButtonProperty, value); }
}
public static readonly DependencyProperty MsgBoxButtonProperty =
DependencyProperty.Register("MsgBoxButton",
typeof(MessageBoxButton),
typeof(ConfirmationRequestor),
new PropertyMetadata(MessageBoxButton.OK));
public MessageBoxImage MsgBoxImage
{
get { return (MessageBoxImage)GetValue(MsgBoxImageProperty); }
set { SetValue(MsgBoxImageProperty, value); }
}
public static readonly DependencyProperty MsgBoxImageProperty =
DependencyProperty.Register("MsgBoxImage",
typeof(MessageBoxImage),
typeof(ConfirmationRequestor),
new PropertyMetadata(MessageBoxImage.Warning));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption",
typeof(string),
typeof(ConfirmationRequestor),
new PropertyMetadata(string.Empty));
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message",
typeof(string),
typeof(ConfirmationRequestor),
new PropertyMetadata(string.Empty));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ConfirmationRequestor), new UIPropertyMetadata(null));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(ConfirmationRequestor), new UIPropertyMetadata(null));
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(ConfirmationRequestor), new UIPropertyMetadata(null));
}
}
I have a viewmodel designed to go with this which is exposed as a property on my window viewmodel. That viewmodel and the control encapsulate the confirmer functionality.
namespace UserInput
{
public class ConfirmationRequestorVM : INotifyPropertyChanged
{
private string caption;
public string Caption
{
get { return caption; }
set
{
caption = value;
NotifyPropertyChanged();
}
}
private string message;
public string Message
{
get { return message; }
set
{
message = value;
NotifyPropertyChanged();
}
}
private MessageBoxButton msgBoxButton;
public MessageBoxButton MsgBoxButton
{
get { return msgBoxButton; }
set
{
msgBoxButton = value;
NotifyPropertyChanged();
}
}
private MessageBoxImage msgBoxImage;
public MessageBoxImage MsgBoxImage
{
get { return msgBoxImage; }
set
{
msgBoxImage = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Usage
In the relevant view
<input:ConfirmationRequestor
ShowConfirmDialog="{Binding ShowConfirmation, Mode=TwoWay}"
MsgBoxImage="{Binding confirmer.MsgBoxImage}"
MsgBoxButton="{Binding confirmer.MsgBoxButton}"
Message="{Binding confirmer.Message}"
Caption="{Binding confirmer.Caption}"
Command="{Binding OkCommand}"
/>
Most of that is fairly self explanatory.
When ShowConfirmation is set to true, that control will show a messagebox.
If the user clicks OK or Yes then the OkCommand will be executed.
Hence if you need deletion confirmation then you show your messagebox and the actual deletion would be in that OkCommand.
If you want more complicated UI then rather than using a messagebox at all you could show a window from similar control.
Let's call this a DialogueController. This could be rather simpler since we can rely on our own UI and bind commands.
This control would have a dependency property for view Type. This being the type of a usercontrol which needs to be shown.
Another bool dependency property and change handler would control showing the type.
When the showdialog bool becomes true.
A method instantiates a window ( or it could be a popup if you prefer ) instantiates a usercontrol of the type specified in our other DP. Sets the window datacontext to our current datacontext.
You could use getwindow to find the parent window and set that to parent of our new (dialogue) window instance.
Thus sharing the parent window viewmodel as datacontext.
Call showdialog on the window. Or you could just call show.
You then have whatever UI you wrote in your usercontrol shown in a window.
It has access to your parent window viewmodel so it can reference any of your data you need.
And... It's Yes or OK button can bind to whatever command you defined in that parent window viewmodel.
You could also do things like select from a list and bind selectedFoo in your parent window viewmodel as well.
If you don't showmodal then when you change shared properties anything bound in your parent window can get those changes.
Like I said though.
I've not really come across much demand for that sort of thing.
There are also some elephants in the not-dialog room.
A pop up referencing it's parent viewmodel.
And
An expander similarly.
And
Just overlaying a panel on top of everything inside your parent window. This is how I have done editing for data in datagrids in a number of apps.
The key thing I found was the realisation:
You can "just" split your code into code before the dialog. Show the dialog. Then the "doing" aspect of any dialog-like-UI can go in a separate command.
OK, it's not exactly a road to damascus moment. But it simplifies things. I like simple. More likely to work.
I have an array of pictureboxes named from B11 (co-ords 1,1) to B55 (co-ords 5,5). I would like to hide these all on startup (and in the middle of running). I was thinking of making an array of the names manually but would it be the best solution?
If they all have a common parent control, such as a panel or groupbox (or even the form):
Parent.SuspendLayout()
For Each pbox As PictureBox in Parent.Controls.OfType(Of PictureBox)()
pbox.Visible = False
Next pbox
Parent.ResumeLayout()
The Suspend/Resume-Layout() is to avoid flickering as you modify a bunch of controls at once.
You could extend the PictureBox class and use event handling to accomplish this by:
Adding a public property to the form to tell if the picture boxes should be shown or hidden.
Adding an event to the form that is raised when the show/hide picture box property is changed.
Extending the PictureBox class so that it subscribes to the event of the parent form.
Setting the visible property of the extended PictureBox class to the show/hide property of the parent form.
When the show/hide flag is changed on the parent form all of the picture boxes will change their visibility property accordingly.
Form Code:
public partial class PictureBoxForm : Form {
public PictureBoxForm() {
InitializeComponent();
this.pictureBoxesAdd();
}
private void pictureBoxesAdd() {
MyPictureBox mp1 = new MyPictureBox();
mp1.Location = new Point(1, 1);
MyPictureBox mp2 = new MyPictureBox();
mp2.Location = new Point(200, 1);
this.Controls.Add(mp1);
this.Controls.Add(mp2);
}
public event EventHandler PictureBoxShowFlagChanged;
public bool PictureBoxShowFlag {
get { return this.pictureBoxShowFlag; }
set {
if (this.pictureBoxShowFlag != value) {
pictureBoxShowFlag = value;
if (this.PictureBoxShowFlagChanged != null) {
this.PictureBoxShowFlagChanged(this, new EventArgs());
}
}
}
}
private bool pictureBoxShowFlag = true;
private void cmdFlip_Click( object sender, EventArgs e ) {
this.PictureBoxShowFlag = !this.PictureBoxShowFlag;
}
}
Extended PictureBox Code:
public class MyPictureBox : PictureBox {
public MyPictureBox() : base() {
this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.ParentChanged += new EventHandler(MyPictureBox_ParentChanged);
}
private void MyPictureBox_ParentChanged( object sender, EventArgs e ) {
try {
PictureBoxForm pbf = (PictureBoxForm)this.Parent;
this.Visible = pbf.PictureBoxShowFlag;
pbf.PictureBoxShowFlagChanged += new
EventHandler(pbf_PictureBoxShowFlagChanged);
} catch { }
}
private void pbf_PictureBoxShowFlagChanged( object sender, EventArgs e ) {
PictureBoxForm pbf = (PictureBoxForm)sender;
this.Visible = pbf.PictureBoxShowFlag;
}
}
...or just put 'em all on a Panel, and change the panel's visibility.
This might sound like a strange request and i'm not sure if it's actually possible, but I have a Silverlight DataPager control where it says "Page 1 of X" and I want to change the "Page" text to say something different.
Can this be done?
In DataPager style there is a part by name CurrentPagePrefixTextBlock by default its value is "Page".
You can refer http://msdn.microsoft.com/en-us/library/dd894495(v=vs.95).aspx for more info.
One of the solution is to extend DataPager
Here is the code to do that
public class CustomDataPager:DataPager
{
public static readonly DependencyProperty NewTextProperty = DependencyProperty.Register(
"NewText",
typeof(string),
typeof(CustomDataPager),
new PropertyMetadata(OnNewTextPropertyChanged));
private static void OnNewTextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var newValue = (string)e.NewValue;
if ((sender as CustomDataPager).CustomCurrentPagePrefixTextBlock != null)
{
(sender as CustomDataPager).CustomCurrentPagePrefixTextBlock.Text = newValue;
}
}
public string NewText
{
get { return (string)GetValue(NewTextProperty); }
set { SetValue(NewTextProperty, value); }
}
private TextBlock _customCurrentPagePrefixTextBlock;
internal TextBlock CustomCurrentPagePrefixTextBlock
{
get
{
return _customCurrentPagePrefixTextBlock;
}
private set
{
_customCurrentPagePrefixTextBlock = value;
}
}
public CustomDataPager()
{
this.DefaultStyleKey = typeof(DataPager);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CustomCurrentPagePrefixTextBlock = GetTemplateChild("CurrentPagePrefixTextBlock") as TextBlock;
if (NewText != null)
{
CustomCurrentPagePrefixTextBlock.Text = NewText;
}
}
}
Now by setting NewText property in this CustomDataPager we can get whatever text we want instead of "Page"
xmlns:local="clr-namespace:Assembly which contains CustomDataPager"
<local:CustomDataPager x:Name="dataPager1"
PageSize="5"
AutoEllipsis="True"
NumericButtonCount="3"
DisplayMode="PreviousNext"
IsTotalItemCountFixed="True" NewText="My Text" />
Now it displays "My Text" Instead of "Page".
But other parts also need to be customised inorder make this work correctly!!
Hope this answers your question
If have an own user control with a DependencyProperty and the corresponding callback method like below:
public partial class PieChart : UserControl
{
public static readonly DependencyProperty RatesProperty = DependencyProperty.Register("Rates", typeof(ObservableCollection<double>), typeof(PieChart),
new PropertyMetadata(new ObservableCollection<double>() { 1.0, 1.0 }, new PropertyChangedCallback(OnRatesChanged)));
[...]
public static void OnRatesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((PieChart)d).drawChart();
}
When using this user control, I bind an ObservableCollection called "Rates" to the RatesProperty. Rates and the related methods look like this:
private ObservableCollection<double> rates;
public ObservableCollection<double> Rates
{
get { return this.rates; }
set
{
if (this.rates != value)
{
this.rates = value;
OnPropertyChanged("Rates");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
When I change the ObservableCollection Rates (e.g. with this.Rates = new ObservableCollection<double>() { 1.0, 2.0 }) the OnRatesChanged() method of the user-control is called like expected. But when I execute the following, it is not called:
this.Rates[0] = (double)1;
this.Rates[1] = (double)2;
OnPropertyChanged("Rates");
I expected that when I raise the PropertyChanged event with the correct property name, the corresponding callback in the user control is always called. Is that wrong?
I found this thread: Getting PropertyChangedCallback of a dependency property always - Silverlight
which covers silverlight but I think then that the same is true in WPF.
So the Framework in the background checks if the bound property (in my example "Rates") changed and only if it changed, it calls the associated callback, correct? Thus changing the elements of my collection has no effect, I always have to change the complete collection?
Thank you!
Your conclusion is right, your OnRatesChanged callback will only be called when the Rates dependency property is set to a new collection (or null).
In order to get notified about changes in the collection, you would also have to register a NotifyCollectionChangedEventHandler:
private static void OnRatesChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var pieChart = (PieChart)d;
var oldRates = e.OldValue as INotifyCollectionChanged;
var newRates = e.NewValue as INotifyCollectionChanged;
if (oldRates != null)
{
oldRates.CollectionChanged -= pieChart.OnRatesCollectionChanged;
}
if (newRates != null)
{
newRates.CollectionChanged += pieChart.OnRatesCollectionChanged;
}
pieChart.drawChart();
}
private void OnRatesCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
...
}
drawChart();
}
It so happened that the application I'm working on doesn't operate on documents, so there's no need in displaying the recently opened documents list in the application menu.
But - annoyingly - there are no properties readily available in the RibbonApplicationMenu class to hide the unused AuxiliaryPane (for which, curiously, the property does exist, but is marked as "internal").
Of course, I can just leave it there - but that's... untidy.
So, here's the solution I came up with.
Hope it will be helpful for anyone else :-)
The general idea is to subclass the RibbonApplicationMenu, find the template child corresponding to the menu's Popup, and overrule its Width (after a number of frustrating experiments it became evident that doing that neither for PART_AuxiliaryPaneContentPresenter nor for PART_FooterPaneContentPresenter - nor for the both - could achieve anything).
Well, without further ado, here's the code:
public class SlimRibbonApplicationMenu : RibbonApplicationMenu
{
private const double DefaultPopupWidth = 180;
public double PopupWidth
{
get { return (double)GetValue(PopupWidthProperty); }
set { SetValue(PopupWidthProperty, value); }
}
public static readonly DependencyProperty PopupWidthProperty =
DependencyProperty.Register("PopupWidth", typeof(double),
typeof(SlimRibbonApplicationMenu), new UIPropertyMetadata(DefaultPopupWidth));
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.DropDownOpened +=
new System.EventHandler(SlimRibbonApplicationMenu_DropDownOpened);
}
void SlimRibbonApplicationMenu_DropDownOpened(object sender, System.EventArgs e)
{
DependencyObject popupObj = base.GetTemplateChild("PART_Popup");
Popup popupPanel = (Popup)popupObj;
popupPanel.Width = (double)GetValue(PopupWidthProperty);
}
}
As a side note, I tried to find any way to resolve the desired width based on the max width of the ApplicationMenu's Items (rather than setting it explicitly through the DependencyProperty in XAML) - but to no avail.
Given my despise to "magic numbers", any suggestion on that will be deeply appreciated.
I know this has been a while, but I've got another solution to this. This one does not provide the Popup width property, instead a ShowAuxilaryPanel boolean. It then goes to Bind the width of the Popup, to the width of the menu item area of the menu.
public class SlimRibbonApplicationMenu : RibbonApplicationMenu
{
public bool ShowAuxilaryPanel
{
get { return (bool)GetValue(ShowAuxilaryPanelProperty); }
set { SetValue(ShowAuxilaryPanelProperty, value); }
}
public static readonly DependencyProperty ShowAuxilaryPanelProperty =
DependencyProperty.Register("ShowAuxilaryPanel", typeof(bool),
typeof(SlimRibbonApplicationMenu), new UIPropertyMetadata(true));
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.DropDownOpened += SlimRibbonApplicationMenu_DropDownOpened;
}
void SlimRibbonApplicationMenu_DropDownOpened(object sender, EventArgs e)
{
DependencyObject popupObj = base.GetTemplateChild("PART_Popup");
Popup panel = (Popup)popupObj;
var exp = panel.GetBindingExpression(Popup.WidthProperty);
if (!this.ShowAuxilaryPanel && exp == null)
{
DependencyObject panelArea = base.GetTemplateChild("PART_SubMenuScrollViewer");
var panelBinding = new Binding("ActualWidth")
{
Source = panelArea,
Mode = BindingMode.OneWay
};
panel.SetBinding(Popup.WidthProperty, panelBinding);
}
else if (this.ShowAuxilaryPanel && exp != null)
{
BindingOperations.ClearBinding(panel, Popup.WidthProperty);
}
}
}
worked for me
<telerik:ApplicationMenu RightPaneVisibility="Collapsed" >