Code:
public partial class MyControl : UserControl
{
int size = 8;
public int Size
{
get { return size; }
set { size = value; Initialize(); }
}
public MyControl()
{
InitializeComponent();
Initialize();
}
void Initialize()
{
// ...
}
}
XAML:
<local:MyControl"/>
or:
<local:MyControl Size="10"/>
When Size property is set in XAML, Initialize is called twice. If I remove Initialize call from InitializeComponent, Initialize is called once from Size setter. But in this case, if Size is not set in XAML, Initialize is not called at all.
Is there any way to write initialization function, which is executed once, after all control properties (if any) are set from XAML?
You may call the Initialize method in a Loaded event handler:
public partial class MyControl : UserControl
{
int size = 8;
public int Size
{
get { return size; }
set { size = value; }
}
public MyControl()
{
InitializeComponent();
Loaded += (o, e) => Initialize();
}
void Initialize()
{
// ...
}
}
In order to make sure the Initialize() method is called only once, although Loaded may be fired more than once, detach the event handler like this:
public MyControl()
{
InitializeComponent();
Loaded += MyControlLoaded;
}
private void MyControlLoaded(object sender, RoutedEventArgs e)
{
Loaded -= MyControlLoaded;
Initialize();
}
Related
I am trying to define some event handlers inside auser class but im not sure how. The MainWindow needs the handler definitions.
public abstract partial class MainWindow: Window {
public User user = new User();
}
public class User {
internal void ExampleMouseEventDown(object sender, MouseEventArgs e) {
// Does stuff;
}
}
Isnt there a way to somehow make this work? Such as:
mainWindow.AddHandleDefinition += ExampleMouseEventDown();
Also tried this:
public partial class MainWindow : Window {
public MainWindow() {
User user = new User(this);
}
}
public class User
{
internal User(MainWindow w)
{
w.AddObserverButton.MouseUp += ExampleMouseEventDown;
}
internal void ExampleMouseEventDown(object sender, MouseEventArgs e)
{
// Does stuff;
}
}
Im working on gauge control.
I need to redraw everything inside when Size or Padding property is changed.
This is how I deal with Size property changes:
public RoundGauge()
{
this.SizeChanged += delegate
{
ReDrawEverything();
};
InitializeComponent();
}
But there is no PaddingChanged event. What can I do with this?
There is no indeed no "PaddingChanged" event but you could use a DependencyPropertyDescriptor to subscribe to changes to a dependency property:
public partial class RoundGauge : UserControl
{
public RoundGauge()
{
InitializeComponent();
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(PaddingProperty, typeof(UserControl));
if (dpd != null)
dpd.AddValueChanged(this, OnPaddingChanged);
}
private void OnPaddingChanged(object sender, EventArgs e)
{
MessageBox.Show("Padding changed!");
}
}
Please refer to the following blog post for more information.
Handling changes to dependency properties in the view: https://blog.magnusmontin.net/2014/03/31/handling-changes-to-dependency-properties/
You can override OnPropertyChanged event:
public partial class RoundGauge : Control
{
public RoundGauge()
{
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == PaddingProperty)
{
Thickness oldPadding = (Thickness)e.OldValue;
Thickness newPadding = (Thickness)e.NewValue;
// ...
}
}
}
I have the view designed for the long running tasks. It has a header and a progress bar.
So the model has the text for a header and a counter for a progress bar and a TotalAmountOfWork field. The model also has
public delegate void TaskCompleted(string resultDescription);
public event TaskCompletedCopyingCompletedEvent;
public event Action UpdateViewState;
When counters change the model invokes UpdateViewState.
ViewModel is subscribed for the events and updates in it's turn the view.
Ok. I have two classes intended to copy files from a hard disk to a flash drive and one class intended for diagnostic information retrieving and this information should be copied to a flash drive finally too.
I want to use them in the same ViewModel, but I can't figure out how to avoid code repeating. I can't figure out how to make it relying on the proper object oriented design.
Those three classes could implement an interface like this:
interface ILongRunningTask {
void DoWork();
}
And then I can implement ViewModel taking ILongRunningTask as an argument.
But look at the name of the name of the interface. It looks too generalized. It seems that something wrong with such an abstraction.
Ok. It seems to me that ViewModel should take a delegate in order to invoke a long running task. But in this case how the ViewModel will interact with the model updating it's properties?
//update
Now, model looks like:
public class FilesCopyingModel : IFilesCopier {
protected int filesCountToCopy;
public int FilesCountToCopy {
get { return filesCountToCopy; }
set {
filesCountToCopy = value;
InvokeUpdateViewState();
}
}
protected int currentProgressValue;
public int CurrentProgressValue {
get { return currentProgressValue; }
set {
currentProgressValue = value;
InvokeUpdateViewState();
}
}
public delegate void CopyingCompleted(string resultDescription);
public event CopyingCompleted CopyingCompletedEvent;
public event Action UpdateViewState;
private readonly IFilesCopier filesCopier;
protected FilesCopyingModel() {
}
public FilesCopyingModel(IFilesCopier filesCopier) {
if (filesCopier == null)
throw new ArgumentNullException("filesCopier");
this.filesCopier = filesCopier;
}
protected static string GetCurrentDateTime() {
return DateTime.Now.ToString("dd.MM.yyyy hh.mm.ss");
}
protected void InvokeCopyCompletedEvent(string resultDescription) {
if (CopyingCompletedEvent != null)
CopyingCompletedEvent(resultDescription);
}
protected void InvokeUpdateViewState() {
if (UpdateViewState != null)
UpdateViewState();
}
protected DriveInfo GetFirstReadyRemovableDrive() {
return
DriveInfo.GetDrives()
.FirstOrDefault(driveInfo => driveInfo.DriveType == DriveType.Removable && driveInfo.IsReady);
}
public void Copy() {
filesCopier.Copy();
}
}
public interface IFilesCopier {
void Copy();
}
public class KFilesCopier : FilesCopyingModel, IFilesCopier {
private string destinationKFilesDirPath;
public new void Copy() {
//some code
}
private static string ComposeDestinationKFilesDirPath(DriveInfo drive) {
//some code
}
}
public class LogsDirCopier : FilesCopyingModel, IFilesCopier {
public readonly string LogsDirPath;
public LogsDirCopier() {
//some code
}
public new void Copy() {
//some code
}
private void InternalCopyLogsDir(string destinationPath) {
//some code
}
private static void CloseStorer(ZipStorer zipStorer) {
//some code
}
private static string ComposeDestinationArchiveFilePath(string destinationPath) {
//some code
}
private void DetermineLogFilesCount() {
//some code
}
ViewModel interact with infrastructure above like this:
public class FilesCopyingViewModel: Screen {
private readonly FilesCopyingModel model;
private readonly IWindowManager windowManager;
public int CurrentProgress {
get { return model.CurrentProgressValue; }
}
public int FilesCountToCopy {
get { return model.FilesCountToCopy; }
}
[ImportingConstructor]
public LongRunningViewModel(IFilesCopier copier) {
model = copier as FilesCopyingModel;
model.CopyingCompletedEvent += CopyingCompletedHandler;
model.UpdateViewState += UpdateViewStateHandler;
windowManager = new WindowManager();
}
private void UpdateViewStateHandler() {
NotifyOfPropertyChange(() => CurrentProgress);
NotifyOfPropertyChange(() => FilesCountToCopy);
}
private void CopyingCompletedHandler(string resultDescription) {
//some code
}
private void RemoveDriveSafely() {
//some code
}
private void PromptEjection(string result) {
//some code
}
private void PromptSuccessEjection() {
//some code
}
private void PromptEjectFlashError() {
//some code
}
protected override void OnActivate() {
try {
var copier = (IFilesCopier) model;
Task.Factory.StartNew(copier.Copy);
}
catch (Exception ex) {
//error handling
}
}
}
These two classes use "Copy" as the name of the method. Now I want to add one more class with very similar behavior, but it seems that it's method should be named like "CollectDiagnosticInfo". Or maybe I can add a class DiagnosticInfoCopier:IFilesCopier and then just do the same. I really don't know, but the sixth sense suggests that there is a smell of some kind.
I'm trying to bind a static property of some class to some control. I've tryied a few implementation but each has its problem:
All examples use the next XAML:
<Label Name="label1" Content="{Binding Path=text}"/>
1st approach - don't use INotifyPropertyChanged
public class foo1
{
public static string text { get; set; }
}
The problem is that when 'text' propery changes the control is not notified.
Second approach - use INotifyPropertyChanged
public class foo1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private static string _text;
public static string text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged("text");
}
}
}
This doesn't compile because OnPropertyChanged() method is not static and it's called within a static method.
Second approach try 2: make OnPropertyChanged() method static => this doesn't compile because OnPropertyChanged() is now static and it tries to use 'PropertyChanged' event which is not static.
Second approach try 3: make 'PropertyChanged' event static => this doesn't compile because the class does not implement 'INotifyPropertyChanged.PropertyChanged' event (the event is defined in 'INotifyPropertyChanged interface is not static but here it is static).
At this point I gave up.
Any Ideas?
I'd suggest you just have an instance-property return your static property like this:
private static string _text;
public string text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged("text");
}
}
However this makes the whole binding comparatively pointless since change notifications are only created in one instance of the class and not every instance. Thus only bindings which bind to the property on the specific instance on which it was changed will update.
A better method would be using a singleton as can be seen here.
Using a singleton is going to be the easiest and cleanest to implement. If you want to go the hard way without using a singleton you can use the following.
Create a static PropertyChangedEventHandler that gets called from your static property. When you create a new instance of your class, register to receive a call back from the static event. When you get the callback, call OnPropertyChanged("text"). The BIG problem with this is you need to use a WeakReference when you register for the static event. Otherwise your object will stay around forever. I skipped this step in the code.
The reason you need to forward to the instance-event is because who ever registered the NotifyPropertyChanged needs to know who the 'sender' (ie the instance of foo1 with the instance-property on it)
public class foo1 : System.ComponentModel.INotifyPropertyChanged
{
// static property
private static string _text = "static string";
public static string static_text
{
get
{
return _text;
}
set
{
_text = value;
OnStaticPropertyChanged("static_text");
}
}
private static System.ComponentModel.PropertyChangedEventHandler staticpropChanged;
static protected void OnStaticPropertyChanged(string pname)
{
System.ComponentModel.PropertyChangedEventArgs e = new System.ComponentModel.PropertyChangedEventArgs(pname);
System.ComponentModel.PropertyChangedEventHandler h = staticpropChanged;
if (h != null)
h(null, e);
}
public foo1()
{
// really should use a weakreference here.. but leaving it out
// for simplicity
staticpropChanged += foo1_staticpropChanged;
}
void foo1_staticpropChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// map the static name to the instance name
if(e.PropertyName == "static_text") OnPropertyChanged("text");
}
// instance-property forwards to static
public string text
{
get { return foo1.static_text; }
set { foo1.static_text = value; }
}
public static String StatusInformation
{
get { return _StatusInformation; }
set { _StatusInformation = value; OnStaticPropertyChanged("StatusText"); }
}
#region Handlig Static Properties Changed
private static System.ComponentModel.PropertyChangedEventHandler staticpropChanged;
static protected void OnStaticPropertyChanged(string pname)
{
System.ComponentModel.PropertyChangedEventArgs e = new System.ComponentModel.PropertyChangedEventArgs(pname);
System.ComponentModel.PropertyChangedEventHandler h = staticpropChanged;
if (h != null)
h(null, e);
}
private void Handler_PropertyChange(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
NotifyPropertyChanged(e.PropertyName);
}
#endregion
public string StatusText
{
get { return ExchangeServices.StatusInformation; }
set { ExchangeServices.StatusInformation = value; }
}
this way i didnt have to do any handling in the event at all.
this was really helpfull to create one status bar for my entire program and update it from anywhere and any user control in my ever expanding program.
Thank you to shimpossible
What can I do if I want to have a text-box representing in real time the value of a loop counter in wpf?
appending working solution:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private delegate void UpdateTextBox(DependencyProperty dp, Object value);
...
private void MyMethod()
{
...
int iMax=...;
...
MyClass iMyClass = new MyClass(arguments);
this.DataContext = iMyClass;
UpdateTextBox updateTBox = new UpdateTextBox(textBlock1.SetValue);
for (int i = 1; i <= iMax; i++)
{
iMyClass.MyClassMethod(i);
Dispatcher.Invoke(updateTBox, System.Windows.Threading.DispatcherPriority.Background, new object[] { MyClass.MyPropertyProperty, iMyClass.myProperty });
}
Here is the code I tried according to your suggestion, but it doesnt work, I get "0" written in the textbox, so I suppose the binding is OK, but the loop doesnt work. I also made the loop to write into another textbox directly by textbox2.text="a" inside the loop, but it didnt work either.
/// <summary>
/// Interaction logic for Window1.xaml
/// DOESNT WORK PROPERLY
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TestClass tTest = new TestClass();
this.DataContext = tTest ;
tTest.StartLoop();
}
}
public class TestClass : DependencyObject
{
public TestClass()
{
bwLoop = new BackgroundWorker();
bwLoop.DoWork += (sender, args) =>
{
// do your loop here -- this happens in a separate thread
for (int i = 0; i < 10000; i++)
{
LoopCounter=i;
}
};
}
BackgroundWorker bwLoop;
public int LoopCounter
{
get { return (int)GetValue(LoopCounterProperty); }
set { SetValue(LoopCounterProperty, value); }
}
public static readonly DependencyProperty LoopCounterProperty = DependencyProperty.Register("LoopCounter", typeof(int), typeof(TestClass));
public void StartLoop()
{
bwLoop.RunWorkerAsync();
}
}
}
Run the loop in a background process (so that the UI can update itself while the loop is running) and
write the loop counter into a property (DependencyProperty or CLR property with INotifyPropertyChanged) which is bound to a TextBox in your user interface. Alternatively, you can directly change the value of the TextBox via Dispatcher.Invoke (this is less elegant, though).
Does this help? Feel free to ask for clarification...
Code example (untested), using a DependencyProperty (which must be bound to a TextBox):
BackgroundWorker bwLoop;
public static readonly DependencyProperty LoopCounterProperty =
DependencyProperty.Register("LoopCounter", typeof(int),
typeof(Window1), new FrameworkPropertyMetadata(0));
public int LoopCounter {
get { return (int)this.GetValue(LoopCounterProperty); }
set { this.SetValue(LoopCounterProperty, value); }
}
private MyWindow() {
...
bwLoop = new BackgroundWorker();
bwLoop.DoWork += (sender, args) => {
...
for (int i = 0; i < someLimit; i++) {
Dispatcher.Invoke(new Action(() => LoopCounter=i));
System.Threading.Thread.Sleep(250); // do your work here
}
}
bwLoop.RunWorkerCompleted += (sender, args) => {
if (args.Error != null)
MessageBox.Show(args.Error.ToString());
};
}
private void StartLoop() {
bwLoop.RunWorkerAsync();
}
you can use Data Binding for continuously updating the text.