Using VS2008, Silverlight 4.
I have created an AttachedProperty, "RequiresRole", that I'm setting in my XAML. If a user isn't in the role defined by that property, the visibility of said control is set to collapsed.
The problem I'm currently having is that the 'UserHasRole' check (within my Authorization class, as part of the RequiredRole_Callback function) is firing when I load the application, but before I have a chance to login, authenticate, and set the Role for the user. So the property is working, because the menu item is not visible, but it's not currently alerting my UI, after authentication, that the role has changed. I know where to implement it, just not how. INotifyPropertyChanged? Not sure how that'd fit.
I have a few properties in my App.xaml.cs, "IsAuthenticated", which is set to true when a user logs in (using a service to call a SPROC), and "Role", which is the return value of a successful call to Logon SPROC.
Relevant Authorization.cs code:
public static readonly DependencyProperty RequiredRoleProperty = DependencyProperty.RegisterAttached(
"RequiredRole",
typeof(string),
typeof(Authorization),
new PropertyMetadata(RequiredRole_Callback));
// This callback will be invoked when some control will receive a value for your 'RequiredRole' property
private static void RequiredRole_Callback(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var uiElement = (UIElement)source;
RecalculateControlVisibility(uiElement);
// also this class should subscribe somehow to role changes and update all control's visibility after role being changed
}
private static void RecalculateControlVisibility(UIElement control)
{
//Authorization.UserHasRole() - is your code to check roles
if (Authorization.UserHasRole(GetRequiredRole(control)))
control.Visibility = Visibility.Visible;
else
control.Visibility = Visibility.Collapsed;
}
private static Boolean UserHasRole(string role)
{
string Role = App.Role;
if (Role.ToLower() == role.ToLower())
{
return true;
}
return false;
}
Relevant MainPage.xaml code:
xmlns:s="clr-namespace:TSMVVM.Authorization"
<HyperlinkButton x:Name="lnkLogin"
HorizontalAlignment="Right"
Tag="Login"
Command="{Binding NavigateCommand}"
s:Authorization.RequiredRole="Azdmin"
CommandParameter="{Binding Tag, ElementName=lnkLogin}"
Content="Login" />
Relevant Mainpage.xaml.cs code:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (!App.IsAuthenticated)
{
ContentFrame.Navigate(new Uri("Login", UriKind.Relative));
}
}
Updated Code
(Note, changed RequiredRole to RequiresRole)
Authorization.cs
public class Authorization
{
#region Attached DP registration
public static string GetRequiresRole(UIElement obj)
{
return (string)obj.GetValue(RequiresRoleProperty);
}
public static void SetRequiresRole(UIElement obj, string value)
{
obj.SetValue(RequiresRoleProperty, value);
}
#endregion
/// Using a DependencyProperty as the backing store for requiresRole. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RequiresRoleProperty = DependencyProperty.RegisterAttached(
"RequiresRole",
typeof(string),
typeof(Authorization),
new PropertyMetadata(RequiresRole_Callback));
// This callback will be invoked when some control will receive a value for your 'RequiredRole' property
private static void RequiresRole_Callback(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var uiElement = (UIElement)source;
if (App.IsAuthenticated)
{
RecalculateControlVisibility(uiElement);
}
else
{
EventHandler eh = null;
eh = delegate
{
RecalculateControlVisibility(uiElement);
((App)Application.Current).Authenticated -= eh;
};
((App)Application.Current).Authenticated += eh;
}
}
private static void RecalculateControlVisibility(UIElement control)
{
//Authorization.UserHasRole() - is your code to check roles
if (Authorization.UserHasRole(GetRequiresRole(control)))
control.Visibility = Visibility.Visible;
else
control.Visibility = Visibility.Collapsed;
}
private static Boolean UserHasRole(string role)
{
if (App.Role != null)
{
string Role = App.Role[0].ToString();
if (Role.ToLower() == role.ToLower())
{
return true;
}
}
return false;
}
}
App.xaml.cs
private static string _role;
public event EventHandler Authenticated = delegate { };
public static string Role
{
get
{
return _role;
}
set
{
_role = value;
}
}
MainPage.xaml:
<HyperlinkButton x:Name="lnkSiteParameterDefinitions"
Style="{StaticResource LinkStyle}"
Tag="SiteParameterDefinitions"
s:Authorization.RequiresRole="Admin"
Content="Site Parameter Definitions"
Command="{Binding NavigateCommand}"
CommandParameter="{Binding Tag, ElementName=lnkSiteParameterDefinitions}"/>
And finally, login.xaml.cs
private void LogonService_LogonCompleted(object sender, TSMVVMLogonSVC.LogonCompletedEventArgs e)
{
ObservableCollection<string> Result = e.Result;
switch (Result[0])
{
case "Logon Failed":
break;
case "Logon Successful":
PageConductor = new Services.PageConductor();
App.CurrentUser = Result[2];
App.Role = Result[1].ToString();
App.IsAuthenticated = true;
Messenger.Default.Send(new TSMVVM.Messages.FrameMessage() { RootFrame = (Frame)this.Parent});
PageConductor.GoToView("Home", "main");
break;
case "Change Password":
break;
}
}
I don't think INotifyPropertyChanged is really what you need in this case. You just need a one shot event when the application has authenticated.
Add an simple event to your App class:-
public event EventHandler Authenticated = delegate { };
Now modify your call back:-
private static void RequiredRole_Callback(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var uiElement = (UIElement)source;
if (App.IsAuthenticated)
{
RecalculateControlVisibility(uiElement);
}
else
{
EventHandler eh = null;
eh = delegate
{
RecalculateControlVisibility(uiElement);
((App)Application.Current).Authenticated -= eh;
};
((App)Application.Current).Authenticated += eh;
}
}
Note the removal of the event handler after it has fired is very important, without it you may well have a memory leak.
Related
I've tried to find a solution myself but I was not able, for some reason the DataContext is not correctly set up in the usercontrol's viewmodel
The idea is to have a single usercontrol that permits to perform a query on a fixed collection and allows the user to drop a treeviewitem that holds an item of the collection (in case the user have the treeview open)
In my main view I've defined :
<views:PortfolioChooserView x:Name="PortfolioChooserView" DataContext="{Binding PortfolioCompleteBox}" Height="25" LoadDefaultValue="True" />
Where PortfolioCompleteBox is a ViewModel defined in the MainViewModel as
public PortfolioChooserViewModel PortfolioCompleteBox
{
get { return GetValue<PortfolioChooserViewModel>(PortfolioChooserViewModelProperty); }
set { SetValue(PortfolioChooserViewModelProperty, value); }
}
public static readonly PropertyData PortfolioChooserViewModelProperty = RegisterProperty("PortfolioCompleteBox", typeof(PortfolioChooserViewModel));
public MainViewModel(ICreditLimitRepository creditLimitRepository, IDynamicContainer dynamicContainer)
{
this.creditLimitRepository = creditLimitRepository;
this.dynamicContainer = dynamicContainer;
LoadCreditLimitsCommand = new Command<object>(OnLoadCreditLimitsExecute, (() => OnLoadCreditLimitsCanExecute));
var viewModelFactory = this.GetServiceLocator().ResolveType<IViewModelFactory>();
PortfolioCompleteBox = viewModelFactory.CreateViewModel<PortfolioChooserViewModel>(null);
Model = new FiltersLoadModel();
}
My problem is that on the PortFolioChooserView I've the DataContext set to null (and I got 2 calls to the PortFolioChooserViewModel, one from the MainViewModel and the other one from the PortFolioChooserView's viewmodel locator)
public partial class PortfolioChooserView
{
private PortfolioChooserViewModel viewModel;
readonly bool isFirstLoad = true;
/// <summary>
/// Initializes a new instance of the <see cref="PortfolioChooserView"/> class.
/// </summary>
///
public PortfolioChooserView()
{
InitializeComponent();
if (isFirstLoad)
{
PortfolioCompleteBox.AllowDrop = true;
DragDropManager.AddPreviewDragOverHandler(PortfolioCompleteBox, OnElementDragOver);
DragDropManager.AddDropHandler(PortfolioCompleteBox, OnElementDrop);
isFirstLoad = false;
this.Loaded += PortfolioChooserView_Loaded;
this.DataContextChanged += PortfolioChooserView_DataContextChanged;
}
}
void PortfolioChooserView_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
int t = 0;
}
void PortfolioChooserView_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
viewModel = (PortfolioChooserViewModel)this.DataContext;
}
private void OnElementDragOver(object sender, Telerik.Windows.DragDrop.DragEventArgs e)
{
var options = Telerik.Windows.DragDrop.DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key) as TreeViewDragDropOptions;
if (options != null)
{
var visual = options.DragVisual as TreeViewDragVisual;
if (visual != null) visual.IsDropPossible = true;
}
e.Handled = true;
}
private void OnElementDrop(object sender, Telerik.Windows.DragDrop.DragEventArgs e)
{
var context = ((IPortfolioAutoComplete)this.DataContext);
context.SetPortfolioAutoCompleteBox(e);
}
public static readonly DependencyProperty LoadDefaultValueProperty = DependencyProperty.Register(
"LoadDefaultValue", typeof(bool), typeof(PortfolioChooserView), new PropertyMetadata(default(bool)));
public bool LoadDefaultValue
{
get { return (bool)GetValue(LoadDefaultValueProperty); }
set { SetValue(LoadDefaultValueProperty, value); }
}
}
What am I doing wrong?
Thanks
Don't try to manage your own vm's
Catel will automatically accept a parent-vm as it's own vm as long as they are compatible. You don't need to handle this manually in your view loading in the view.
Instead of creating a VM in the parent VM, use a model only (so the vm only cares about what the VM itself should do). Then set the DC of the PortfolioChooserView to the model. Then the vm of the child view can accept the model in the ctor and be managed on it's own.
There are much better ways to communicate between vm's then trying to micro-manage like you are doing now. As always, see the docs.
I want to create a custom class of a CommandBinding, where a RelayCommand of my ViewModel is executed when the RoutedCommand is executed.
Currently there is only the possibility to create CommandBindings which have Executed methods in the codebehind class.
Example:
<CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandHandler"
CanExecute="CanExecuteHandler"/>
This needs the CloseCommandHandler methode in the code behind.
I would want to write the following.
<CommandBinding RoutedCommand="ApplicationCommands.Close" Command={Binding Path=CloseCommand}/>
The only problem is that i can't find the bubble down and up event of the RoutedCommands.
There is no
OnPreviewCommand(object command, object commandParammeter)
OnCommand(object command, object commandParammeter)
Where is the RoutedCommand bubble down and up handled?
I came up with a solutions of my own. Its not the most beautiful, but its working.
I derived from the ContentControl. The new Control has a RoutedCommandBindings property, which contains a list of "sort of" CommandBindings between RoutedCommands and RelayCommands.
It can be used like this.
<CSControls:RoutedCommandBinder>
<CSControls:RoutedCommandBinder.RoutedCommandBindings>
<CSControls:RoutedCommandBindingCollection>
<CSControls:RoutedCommandBinding RoutedCommand="{x:Static ApplicationCommands.New}" Command="{Binding Path=AddInstanceCommand}"/>
</CSControls:RoutedCommandBindingCollection>
</CSControls:RoutedCommandBinder.RoutedCommandBindings>
<CSControls:RoutedCommandBinder.Content>
<!-- Every RoutedCommand of type ApplicationCommands.New will execute the binded RelayCommand "AddInstanceCommand-->
</CSControls:RoutedCommandBinder.Content>
</CSControls:RoutedCommandBinder>
Here is the CustomControl code.
public class RoutedCommandBinder : ContentControl
{
public RoutedCommandBinder()
{
this.DataContextChanged += RoutedCommandBinder_DataContextChanged;
}
public static readonly DependencyProperty RoutedCommandBindingsProperty = DependencyProperty.Register("RoutedCommandBindings", typeof(RoutedCommandBindingCollection), typeof(RoutedCommandBinder), new PropertyMetadata(new RoutedCommandBindingCollection(), OnRoutedCommandBindingsChanged));
public RoutedCommandBindingCollection RoutedCommandBindings
{
get { return (RoutedCommandBindingCollection)this.GetValue(RoutedCommandBindingsProperty); }
set { SetValue(RoutedCommandBindingsProperty, value); }
}
private static void OnRoutedCommandBindingsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RoutedCommandBinder binder = (RoutedCommandBinder)d;
binder.CommandBindings.Clear();
SetDataContextForCommandBindings(binder);
if (e.NewValue != null)
{
RoutedCommandBindingCollection bindings = (RoutedCommandBindingCollection)e.NewValue;
foreach (RoutedCommandBinding binding in bindings)
{
binder.CommandBindings.Add(new CommandBinding(binding.RoutedCommand, binder.RoutedCommandExecuted, binder.CanExecuteRoutedEventHandler));
}
}
}
private void RoutedCommandBinder_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetDataContextForCommandBindings(this);
}
private static void SetDataContextForCommandBindings(RoutedCommandBinder binder)
{
if (binder.DataContext != null && binder.RoutedCommandBindings != null)
{
foreach (RoutedCommandBinding binding in binder.RoutedCommandBindings)
{
binding.DataContext = binder.DataContext;
}
}
}
private void RoutedCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
RoutedCommandBinding binding = this.RoutedCommandBindings.FirstOrDefault(t => t.RoutedCommand == e.Command);
if (binding != null)
{
binding.Command.Execute(e.Parameter);
}
}
private void CanExecuteRoutedEventHandler(object sender, CanExecuteRoutedEventArgs e)
{
RoutedCommandBinding binding = this.RoutedCommandBindings.FirstOrDefault(t => t.RoutedCommand == e.Command);
if (binding != null)
{
e.CanExecute = binding.Command.CanExecute(e.Parameter);
}
}
}
public class RoutedCommandBindingCollection : List<RoutedCommandBinding>
{
}
public class RoutedCommandBinding : FrameworkElement
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(RoutedCommandBinding), new PropertyMetadata(null));
public ICommand Command
{
get { return (ICommand)this.GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty RoutedCommandProperty = DependencyProperty.Register("RoutedCommand", typeof(RoutedCommand), typeof(RoutedCommandBinding), new PropertyMetadata(null));
public RoutedCommand RoutedCommand
{
get { return (RoutedCommand)this.GetValue(RoutedCommandProperty); }
set { SetValue(RoutedCommandProperty, value); }
}
}
Recently i was developing a custom control in Silverlight, I created custom dependency property which is of type ObservableCollection. I have another 2 custom dependency properties of type strings. My requirement is on addition of any item to collection, I have to fire collectionChanged Event , in this event handler, i want to update the other 2 dependency properties.
public static readonly DependencyProperty itemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<ValidationErrorMessage>), typeof(SummaryUserControl), new PropertyMetadata(new ObservableCollection<ValidationErrorMessage>(), new PropertyChangedCallback(fun1)));
public ObservableCollection<ValidationErrorMessage> Items
{
get
{
return (ObservableCollection<ValidationErrorMessage>)base.GetValue(itemsProperty);
}
set
{
base.SetValue(itemsProperty, value);
}
}
public static void fun1(object sender, DependencyPropertyChangedEventArgs evt)
{
var newValue = evt.NewValue as ObservableCollection<ValidationErrorMessage>;
if(newValue!=null)
newValue.CollectionChanged += new NotifyCollectionChangedEventHandler(CollectionChangedHandler);
var oldValue = evt.OldValue as ObservableCollection<ValidationErrorMessage>;
if(oldValue!=null)
oldValue.CollectionChanged -= new NotifyCollectionChangedEventHandler(CollectionChangedHandler);
}
static void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
var newItems = e.NewItems as ObservableCollection<ValidationErrorMessage>;
foreach (var item in newItems)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
static void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
public static readonly DependencyProperty headerProperty = DependencyProperty.Register("Header", typeof(String), typeof(SummaryUserControl), new PropertyMetadata(String.Empty, null));
public String Header
{
get
{
return (String)base.GetValue(headerProperty);
}
set
{
base.SetValue(headerProperty, value);
RaisePropertyChange("Header");
}
}
public static readonly DependencyProperty messageTypeProperty =
DependencyProperty.Register("MessageType", typeof(MessageEnumType), typeof(SummaryUserControl), new PropertyMetadata(MessageEnumType.Error, null));
public MessageEnumType MessageType
{
get { return (MessageEnumType)GetValue(messageTypeProperty); }
set { SetValue(messageTypeProperty, value); RaisePropertyChange("MessageType"); }
}
How can I change the values of the dependency properties messageType and Header? I'm unable to access those properties in either the CollectionChanged or NotifyPropertyChanged event since all those events are static. I cannot access the instance within these static event handlers.
I tried to fix the problem with a converter, but my curosity on Silverlight makes me want to use the above approach. How can I set values for those dependency properties within CollectionChanged event or NotifyPropertyChanged events?
The sender in your static fun1 method should be the instance of the class which declares the itemsProperty DependencyProperty. Therefore you can access the concrete instance with casting the sender to your class.
public static void fun1(object sender, DependencyPropertyChangedEventArgs evt)
{
MyClass concreteInstance = sender as MyClass;
if(concreateInstance != null)
{
[...your code...]
}
}
OK - So I almost have this working. I just need to know who to get the usercontrol to let the viewmodel of the consuming view know there has been a change. Check this out - here is xaml from the consuming view.
<StackPanel>
<pfControls:TriChoiceUserControl Text="{Binding Path=SampleText}" State="{Binding CurrentState}"/>
</StackPanel>
Here is the viewmodel code
class MainWindowViewModel: INotifyPropertyChanged
{
private bool? currentState;
public bool? CurrentState
{
get { return currentState; }
set {
currentState = value;
OnPropertyChanged("CurrentState");
}
}
public string SampleText { get { return "Hi there"; } }
public MainWindowViewModel()
{
CurrentState = false;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Now on the initial load of the ViewModel you can see that Current state is false and indeed the control I ends up with the false check box checked (there are three check boxes, one for yes, one for no and one for na - don't ask me, that is what they told me to do). Problem is that when I check the first one (true in this case) the user control is working in that it goes and unchecks the false check box but and changes the state property but my viewmodel for the consuming view never gets notified. I feel like I am so close... Here is the code for the user control.
public partial class TriChoiceUserControl : UserControl, INotifyPropertyChanged
{
#region Fields (5)
public static readonly DependencyProperty StateProperty = DependencyProperty.Register("State", typeof(bool?), typeof(TriChoiceUserControl),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeState)));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(String), typeof(TriChoiceUserControl),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeText)));
#endregion Fields
public TriChoiceUserControl()
{
InitializeComponent();
}
public bool? State
{
get
{
return (bool?)GetValue(StateProperty);
}
set
{
SetValue(StateProperty, value);
NotifyPropertyChanged("State");
}
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
private static void ChangeState(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as TriChoiceUserControl).UpdateCheckState((bool?)e.NewValue);
}
private static void ChangeText(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as TriChoiceUserControl).UpdateText(e.NewValue.ToString());
}
private void UpdateText(string newText)
{
label1.Content = newText;
}
private void UpdateCheckState(bool? newState)
{
if (newState != null)
{
if ((bool)newState)
{
chkYes.IsChecked = true;
chkNo.IsChecked = false;
chkNa.IsChecked = false;
}
else
{
chkYes.IsChecked = false;
chkNo.IsChecked = true;
chkNa.IsChecked = false;
}
}
else
{
chkYes.IsChecked = false;
chkNo.IsChecked = false;
chkNa.IsChecked = true;
}
State = newState;
}
private void chkYes_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(true);
}
private void chkNo_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(false);
}
private void chkNa_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(null);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Here is the XAML for the user control.
Thanks for any input.
All of this works just fine, I had lost sight of the fact that the default mode is "oneWay" on the binding - duh - I set Mode=TwoWay and no everything works. But that OK, I don't mind saying duh, it usually means I have found the answer :)
In the code below you can see what I'm trying to do, but it doesn't work. I want an event that I can attach to outside of my user control and fires whenever the dependency property changes.
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value"
, typeof(Double)
, typeof(ucSlider)
, new PropertyMetadata(50d, new PropertyChangedCallback(OnValueChanged)));
public Double Value
{
get { return (Double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public event PropertyChangedCallback OnValueChanged;
Dependency properties are static, but your event is related with the instance of the class. So you need an intermediate method between the static property and the event of instance.
In this example I pass the static method OnValuePropertyChanged as a callback parameter and inside it I raise the event:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value"
, typeof(Double)
, typeof(ucSlider)
, new PropertyMetadata(50d, new PropertyChangedCallback(OnValuePropertyChanged)));
public Double Value
{
get { return (Double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static void OnValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var sl = sender as ucSlider;
if (sl != null)
sl.RaiseValueChangedEvent(e);
}
private void RaiseValueChangedEvent(DependencyPropertyChangedEventArgs e)
{
if(this.OnValueChanged != null)
this.OnValueChanged(this, e);
}
public event PropertyChangedCallback OnValueChanged;