I have a UserControl with a Slider which I'd like to handle RoutedEvent. Button was no problem and it works fine, since there are no args.
This is my Button handler:
public static readonly RoutedEvent OnOffClickEvent = EventManager.RegisterRoutedEvent(
nameof(OnOffClick), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ColorLightControl));
public event RoutedEventHandler OnOffClick
{
add => AddHandler(OnOffClickEvent, value);
remove => RemoveHandler(OnOffClickEvent, value);
}
private void ButtonOnOff_Click(object sender, RoutedEventArgs e) =>
RaiseEvent(new RoutedEventArgs(OnOffClickEvent));
But banging my head on the table because can't handle Slider's ValueChanged event. This is what I coded but it doesn't work on execution.
public static readonly RoutedEvent ColorSlideEvent = EventManager.RegisterRoutedEvent(
nameof(ColorSlide), RoutingStrategy.Bubble, typeof(EventHandler<RoutedPropertyChangedEventArgs<double>>), typeof(ColorLightControl));
public event EventHandler<RoutedPropertyChangedEventArgs<double>> ColorSlide
{
add => AddHandler(ColorSlideEvent, value);
remove => RemoveHandler(ColorSlideEvent, value);
}
private void SliderColor_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) =>
RaiseEvent(new RoutedPropertyChangedEventArgs<double>(e.OldValue, e.NewValue, ColorSlideEvent));
I get this error and I'm pretty sure it's because ColorSlide is not declated as RoutedEventHandler but not sure how to pass args. Any help appreciated. Thanks
System.InvalidCastException: 'Unable to cast object of type 'System.EventHandler1[System.Windows.RoutedPropertyChangedEventArgs1[System.Double]]' to type 'System.Windows.RoutedPropertyChangedEventHandler`1[System.Double]'.'
The error message tells the whole story:
either use the appropriate event handler RoutedPropertyChangedEventHandler (instead of EventHadler<T>) or create a custom class that extends RoutedEventArgs.
The reason is that RoutedPropertyChangedEventArgs<T> overrides the virtual RoutedEventArgs.InvokeEventHandler method (which is highly recommended to do so). In this override you usually cast the Delegate to a specific type (in place of reflection). This is where the coupling between RoutedPropertyChangedEventArgs and RoutedPropertyChangedEventHandler is created and where the exception you have encountered is thrown (as a result of the failed explicit type cast).
Since the default implementation of RoutedEventArgs.InvokeEventHandler uses reflection, you are advised to provide a specialized override to improve the performance (as you eliminate the reflection required to write a generic event invocator).
An example to create a custom routed event args type including the recommended RoutedEventArgs.InvokeEventHandler override:
// Optional delegate as an alternative to 'EventHandler<SliderValueChangedRoutedEventArgs<TValue>>'.
public delegate void SliderValueChangedRoutedEventHandler<TValue>(object sender, SliderValueChangedRoutedEventArgs<TValue> e);
// Routed event args that supports the 'SliderValueChangedRoutedEventHandler<TValue>' delegate
// and the 'EventHandler<SliderValueChangedRoutedEventArgs<TValue>>' delegate
public class SliderValueChangedRoutedEventArgs<TValue> : RoutedEventArgs
{
public SliderValueChangedRoutedEventArgs()
{
}
public SliderValueChangedRoutedEventArgs(RoutedEvent routedEvent) : base(routedEvent)
{
}
public SliderValueChangedRoutedEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source)
{
}
public SliderValueChangedRoutedEventArgs(RoutedEvent routedEvent, object source, TValue value) : base(routedEvent, source)
{
this.Value = value;
}
protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget)
{
if (genericHandler is EventHandler<SliderValueChangedRoutedEventArgs<TData>> defaultHandler)
{
defaultHandler.Invoke(genericTarget, this);
}
else
{
var strongTypedHandler = (SliderValueChangedRoutedEventHandler<TData>)genericHandler;
strongTypedHandler.Invoke(genericTarget, this);
}
}
public TValue Value { get; }
}
Related
I am developing a WPF/NetCore3.1 application with MVVM. In the view there is a button which is bound to a RelayCommand. The ViewModel is in a different class library than the View. In the ViewModel a timer is started which increments a variable every second and triggers the CanExecuteChanged event of the RelayCommand.
Here is my ViewModel:
public ImportExportViewModel()
{
MakeOfferCommand = new RelayCommand(MakeOffer, CanMakeOffer);
Timer t = new Timer(1000);
t.Elapsed += T_Elapsed;
t.Start();
}
private void T_Elapsed(object sender, ElapsedEventArgs e)
{
ElapsedTime++;
MakeOfferCommand.RaiseCanExecuteChanged();
}
private void MakeOffer()
{
// TODO Make Offer
}
private bool CanMakeOffer()
{
return ElapsedTime < 60;
}
And here the RaiseCanExecuteChanged:
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, new EventArgs());
}
}
But here I get an InvalidOperationException: the calling thread cannot access this object because the object is owned by another thread.
Normally I would execute a Dispatcher.Invoke() here, but that seems not to exist in .NetCore3.1.
Can anyone tell me how I can still make cross-thread calls?
You could inject your view model with an IDispatch interface that you implement in each platform:
Interface:
public interface IDispatch
{
bool CheckAccess();
void Invoke(Action action);
}
View Model:
public IDispatch Dispatch { get; set; }
private void T_Elapsed(object sender, ElapsedEventArgs e)
{
if (Dispatch != null && !Dispatch.CheckAccess())
Dispatch.Invoke(new Action(() => { /* do something */ }));
...
}
WPF implementation:
public class WpfDispatch : IDispatch
{
private readonly Dispatcher _dispatcher;
public WpfDispatch(Dispatcher dispatcher) =>
_dispatcher = dispatcher;
public bool CheckAccess() => _dispatcher.CheckAccess();
public void Invoke(Action action) => _dispatcher.Invoke(action);
}
I have derived a custom bootstrapper from Caliburn.Micro.Bootstrapper, I notice it can take a generic type parameter - what is this for?
public class SimpleInjectorBootstrapper : Caliburn.Micro.Bootstrapper
{
private Container container;
public SimpleInjectorBootstrapper()
{
}
protected override void Configure()
{
this.container = new Container();
this.container.Register<IWindowManager, WindowManager>();
this.container.Register<IEventAggregator, EventAggregator>();
this.container.Register<IAppViewModel, AppViewModel>();
}
protected override object GetInstance(Type serviceType, string key)
{
return this.container.GetInstance(serviceType);
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return this.container.GetAllInstances(serviceType);
}
protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
{
base.OnStartup(sender, e);
var appViewModel = this.container.GetInstance<IAppViewModel>();
var windowManager = this.container.GetInstance<IWindowManager>();
windowManager.ShowWindow(appViewModel);
}
}
It's a view model type to use as your starting view model. Caliburn.Micro will resolve the type from the IoC container, and in WPF use the WindowManager to display the root view. Bascially what you're doing in your OnStartup override.
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...]
}
}
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;
I am using PRISM 4 and got my head around almost all features, however as soon as I would like to inject my DomainContext class (RIA) into my view model, the hell breaks loose. :) It would be great if an experienced Unity/Prism developer could give me an advice how to proceed.
Within my bootstrapper, I am registering the required class in Unity Container like this:
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType<SCMDomainContext>();
}
Within the NavigationModule, I have the following in the ctor to register the NavigationView with a particular region.
public NavigationModule(IUnityContainer container, IRegionManager regionManager)
{
_container = container;
_regionManager = regionManager;
_regionManager.RegisterViewWithRegion(Constants.NavRegion, () => _container.Resolve<NavigationView>());
}
The View takes the View Model as dependency:
public NavigationView(NavigationViewModel viewModel)
{
InitializeComponent();
Loaded += (s, e) =>
{
DataContext = viewModel;
};
}
The ViewModel has the following:
public NavigationViewModel(SCMDomainContext context)
{
_context = context;
ConstructCommon();
}
As soon as I comment this ctor out and put a en empty ctor, it is all fine, for some reason I can't resolve the SCMDomainContext class. Which is the one you add to have the Domain Context created for you provided by Ria Services.
Since I am using Silverlight, I can't see the stack trace to follow the exception, all I get is this message on a page. What am I missing please?
Microsoft JScript runtime error: Unhandled Error in Silverlight Application An exception occurred while initializing module 'NavigationModule'.
- The exception message was: Activation error occured while trying to get instance of type NavigationModule, key ''
Check the InnerException property of the exception for more information. If the exception occurred
while creating an object in a DI container, you can exception.GetRootException() to help locate the
root cause of the problem. at Microsoft.Practices.Prism.Modularity.ModuleInitializer.HandleModuleInitializationError(ModuleInfo moduleInfo, String assemblyName, Exception exception)
at Microsoft.Practices.Prism.Modularity.ModuleInitializer.Initialize(ModuleInfo moduleInfo)
at Microsoft.Practices.Prism.Modularity.ModuleManager.LoadModulesThatAreReadyForLoad()
at Microsoft.Practices.Prism.Modularity.ModuleManager.IModuleTypeLoader_LoadModuleCompleted(Object sender, LoadModuleCompletedEventArgs e)
at Microsoft.Practices.Prism.Modularity.XapModuleTypeLoader.RaiseLoadModuleCompleted(LoadModuleCompletedEventArgs e)
at Microsoft.Practices.Prism.Modularity.XapModuleTypeLoader.HandleModuleDownloaded(DownloadCompletedEventArgs e)
at Microsoft.Practices.Prism.Modularity.XapModuleTypeLoader.IFileDownloader_DownloadCompleted(Object sender, DownloadCompletedEventArgs e)
at Microsoft.Practices.Prism.Modularity.FileDownloader.WebClient_OpenReadCompleted(Object sender, OpenReadCompletedEventArgs e)
at System.Net.WebClient.OnOpenReadCompleted(OpenReadCompletedEventArgs e)
at System.Net.WebClient.OpenReadOperationCompleted(Object arg)
Your help on this is highly appreciated,
Kave
I can't see much wrong here. But having said that, I'm using the Initialize method from the interface in the following way to register types and views for regions:
#region properties
[Dependency]
public IUnityContainer Container { get; set; }
[Dependency]
public IRegionManager RegionManager { get; set; }
#endregion
public virtual void Initialize()
{
this.Container.RegisterType<NavigationViewModel>(new ContainerControlledLifetimeManager());
this.Container.RegisterType<NavigationView>(new ContainerControlledLifetimeManager());
this.RegionManager.RegisterViewWithRegion(Constants.NavRegion, () => this.Container.Resolve<NavigationView>());
}
Not sure whether it makes a difference if you don't explicitly register the ViewModel and the View type. Personally I prefer to have control over the way how a type gets resolved by the container.
In fact its best to create a layer for the DomainContext like this. Then its easily resolvable by an IoC:
public class ContactModuleService : IContactModuleService
{
readonly SCMDomainContext _context = new SCMDomainContext();
#region Implementation of IContactModuleService
public EntitySet<Contact> Contacts
{
get { return _context.Contacts; }
}
public EntityQuery<Contact> GetContactsQuery()
{
return _context.GetContactsQuery();
}
public SubmitOperation SubmitChanges(Action<SubmitOperation> callback, object userState)
{
return _context.SubmitChanges(callback, userState);
}
public SubmitOperation SubmitChanges()
{
return _context.SubmitChanges();
}
public LoadOperation<TEntity> Load<TEntity>(EntityQuery<TEntity> query, Action<LoadOperation<TEntity>> callback, object userState) where TEntity : Entity
{
return _context.Load(query, callback, userState);
}
public LoadOperation<TEntity> Load<TEntity>(EntityQuery<TEntity> query) where TEntity : Entity
{
return _context.Load(query);
}
#endregion
}