WPF send event from App to User control - c#

In my application, I use an event to check network status. In the MainWindow, I instantiate some user controls (for example, I have 3 child user controls), and in one of these child controls, I need to catch the event from the App to this specific user control.
In the App I use this to start:
protected override void OnStartup(StartupEventArgs e)
{
NetworkStatus.AvailabilityChanged +=
new NetworkStatusChangedHandler(DoAvailabilityChanged);
base.OnStartup(e);
}
static void DoAvailabilityChanged(
object sender, NetworkStatusChangedArgs e)
{
//this method will send a notification
//ReportAvailability();
}
When I catch this event, I need to change the brushes in my StackPanel. After I have created the two brushes, how I can change them? I have seen some information about custom triggers. How can I use those in my StackPanel?

I used Tunneling events.
In child viewmodel:
#region Events
public readonly static RoutedEvent NetworkStatusEvent =
EventManager.RegisterRoutedEvent(
"NetworkStatusEvent",
RoutingStrategy.Tunnel,
typeof(RoutedEventHandler),
typeof(NetworkStatusViewModel));
#endregion
public void NetworkStatus_Changed(Object sender, RoutedEventArgs e)
{
Image = "home-scanner";
IsAvailable = NetworkStatus.IsAvailable ? true : false;
TextLegend = "sfsdfhf";
//RaiseEvent(new RoutedEventArgs(NetworkStatusViewModel.GreetEvent, this));
e.Handled = true;
}
In MainViewModel:
private static NetworkStatusViewModel networkStatusViewModel = new NetworkStatusViewModel();
public static NetworkStatusViewModel NetworkStatusViewModel
{
get
{
return networkStatusViewModel;
}
//set {
// networkStatusViewModel = value;
//}
}
I hope this helps.

Related

Passing data between usercontrols c#

It's known that there are some solutions similar to this one, but I can't solve my problem with them.
I have two user controls:
The first one makes a Report object.
The second one shows it.
I have a main Form that links both controls.
These two controls are created in a DLL, and are added to the
main form like this:
//ADDS THE FIRST CONTROL TO THE PANEL CONTROL
myDll.controlUserReport userControlA = new myDll.controlUserReport();
panelControl1.Controls.Add(userControlA);
userControlA.Dock = DockStyle.Left;
//ADDS THE SECOND CONTROL TO THE PANEL CONTROL
myDll.controlDocViewer userControlB = new myDll.controlDocViewer();
panelControl1.Controls.Add(userControlB);
userControlB.Dock = DockStyle.Fill;
How can I pass the Report object, which is created in the first control controlUserReport when I click over a button, to the other user control controlDocViewer to show it?
You should use events for this. In UserControlA declare the event:
//Declare EventHandler outside of class
public delegate void MyEventHandler(object source, Report r);
public class UserControlA
{
public event MyEventHandler OnShowReport;
private void btnShowReport_Click(object sender, Report r)
{
OnShowReport?.Invoke(this, this.Report);
}
}
In UserControlB subscribe to the event and show the report:
public class UserControlB
{
// Do it in Form_Load or so ...
private void init()
{
userControlA.OnShowReport += userControlA_OnShowReport;
}
private void userControlA_OnShowReport(object sender, Report r)
{
// Show the report
this.ShowReport(r);
}
}
the post above is good except init() method should not be in ControlB, but in the parent form, something like this:
public class frmMain : Form
{
private void frmMain_Load(object sender, EventArgs e)
{
// subscribe/glue
userControlA.OnShowReport += userControlB.OnShowReport;
userControlB.OnShowReport += userControlA.OnShowReport;
}
public class UserControlA
{
public event EventHandlerNodeCopy OnDataCopy;
public TreeNode NodeCopied { get; set; }
private void some_method(string z, TreeNode trn)
{
OnDataCopy?.Invoke(this, trn);
...
}
public void frmJsTree_OnDataCopy(object source, TreeNode tn)
{
NodeCopied = tn;
}
public class UserControlB
{
public event EventHandlerNodeCopy OnDataCopy;
public TreeNode NodeCopied { get; set; }
private void another_method(int i, TreeNode trn)
{
OnDataCopy?.Invoke(this, trn);
...
}
public void frmJsTree_OnDataCopy(object source, TreeNode tn)
{
NodeCopied = tn;
}
enter code here
Another approach is using BehaviorSubject (requires System.Reactive). Once the data is added in the BehaviorSubject, all places that subscribe can see the info. A very basic example:
Create a class to represent your data. Ex:
DataService.cs
public static BehaviorSubject<YourDataType> MyAwesomeData { get; } = new BehaviorSubject<YourDataType> (null);
In You UserControlA (maybe in the clickEvent from the button) or whatever:
private void btnShowReport_Click(object sender, MouseButtonEventArgs e)
{
// Do some stuffs to prepare the data...
YourDataType myDataReportPrepared = null; // something;
// Here you update your DataInfo on BehaviorSubject
DataService.MyAwesomeData.OnNext(myDataReportPrepared);
}
Finally every place in your solution that uses subscribe in that Subject will listen to that data, like for example in your UserControlB:
// This will trigger every time MyAwesomeData.OnNext() is executed.
DataService.MyAwesomeData.Subscribe(item =>
{
if (item != null){
// Do something with it...Like populate some Datagrid...
}
});

UWP Adding event handler in base page cause AccessViolationException

I have 'BasePage' class which is base class for all other pages in my project.
In initialization, I'm adding EventHandler for 'SystemNavigationManager' for event 'BackRequest'. For some reason that line id causing 'AccessViolationException' when XAML designer is trying to render XAML of class that extends 'BasePage'
I'm not familiar with UWP, so I'll be very grateful for tips.
BasePage
public class BasePage: Page {
internal string title = "";
internal HeaderView headerView;
public BasePage() {
this.Loaded += BasePage_Loaded;
// FIXME: For some reason if this line is uncommented then xaml designer fails.
SystemNavigationManager.GetForCurrentView().BackRequested += BasePage_BackRequested;
}
private void BasePage_BackRequested(object sender, BackRequestedEventArgs e) {
bool handled = e.Handled;
this.BackRequested(ref handled);
e.Handled = handled;
}
private void BackRequested(ref bool handled) {
//Get a hold of the current frame so that we can inspect the app back stack.
if (this.Frame == null)
return;
// Check to see if this is the top-most page on the app back stack.
if (this.Frame.CanGoBack && !handled) {
// If not, set the event to handled and go back to the previous page in the app.
handled = true;
this.Frame.GoBack();
}
}
private void setupPageAnimation() {
TransitionCollection collection = new TransitionCollection();
NavigationThemeTransition theme = new NavigationThemeTransition();
var info = new ContinuumNavigationTransitionInfo();
theme.DefaultNavigationTransitionInfo = info;
collection.Add(theme);
this.Transitions = collection;
}
private void BasePage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e) {
setupPageAnimation();
}
}
SOLUTION
Just like Ivan said, final code looks like this. Without of a trace of bug.
BasePage
public BasePage() {
this.Loaded += BasePage_Loaded;
}
protected override void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
SystemNavigationManager.GetForCurrentView().BackRequested += BasePage_BackRequested;
}
protected override void OnNavigatedFrom(NavigationEventArgs e) {
base.OnNavigatedFrom(e);
SystemNavigationManager.GetForCurrentView().BackRequested -= BasePage_BackRequested;
}
You shouldn't subscribe to back events on constructor but OnNavigatedTo and unsubscribe in OnNavigatedFrom. Even if it didn't crash it would cause a lot of problems because your back logic would be activated on all previous pages when you press the back button.

RichTextBox blocks DragDrop event

I have DragDrop and DragEnter events on my SplitContainer.Panel:
splitContainer.Panel.DragDrop += new System.Windows.Forms.DragEventHandler(this.splitContainerPanelDragDrop);
splitContainer.Panel.DragEnter += new System.Windows.Forms.DragEventHandler(this.splitContainerPanelDragEnter);
It works perfect with every control inside SplitContainer.Panel except RichTextBox controls.
How it looks like:
So DragDrop/DragEnter works perfectly in every control inside SplitContainer except controls which is marked yellow color.
What I tried:
1) Set
RichTextBox.AllowDrop = false;
So I even DragEnter is unavailable with "action is not allowed" cursor.
2) Set
RichTextBox.AllowDrop = true;
After this cursor is ok, but it doesnt work because expects additional DragEventHandler in other case it doesnt work.
3) Set
RichTextBox.EnableAutoDragDrop=false;
RichTextBox.AllowDrop=true;
Same result as 2) variant.
I dont want to set DragDrop/DragEnter event for every RichTextBox inside SplitContainer because inside FlowLayoutPanel they are created dynamically.
The question is: is there any method like e.PreventDefault analog in C#? Or what can I do except setting events for every RichTextBox to make it work?
This worked for me
I created 2 custom controls
Custom SplitControl
public partial class SplitControlCustom : SplitContainer
{
public SplitControlCustom()
{
InitializeComponent();
}
public void ForceDrageDrop(DragEventArgs eventArgs)
{
OnDragDrop(eventArgs);
}
public void ForceDragEnter(DragEventArgs eventArgs)
{
OnDragEnter(eventArgs);
}
}
Custom RichTextBox
public partial class RichTextBoxCustom : RichTextBox
{
public RichTextBoxCustom()
{
InitializeComponent();
this.AllowDrop = true;
}
protected override void OnDragEnter(DragEventArgs drgevent)
{
SplitControlCustom parentSplitControl = Parent.Parent as SplitControlCustom;
if (parentSplitControl != null)
{
parentSplitControl.ForceDragEnter(drgevent);
}
}
protected override void OnDragDrop(DragEventArgs drgevent)
{
SplitControlCustom parentSplitControl = Parent.Parent as SplitControlCustom;
if (parentSplitControl != null)
{
parentSplitControl.ForceDrageDrop(drgevent);
}
}
}
Please let me know if it worked
I don't see how you can make this work directly. But then, since you are already willing to add a few lines of code while generating the controls, why not add the necessary events via a few lines of Lambda..:
Let's assume you have just created a RichTextBox and are ready to add it to some Controls collection..:
RichTextBox richTextBox = new RichTextBox ();
...
richTextBox.AllowDrop = true;
richTextBox.DragEnter += (ss, ee) => { ee.Effect = DragDropEffects.Copy; };
richTextBox.DragOver += (ss, ee) => { ee.Effect = DragDropEffects.Copy; };
richTextBox.DragDrop += (ss, ee)
=> { splitContainer.Panel_DragDrop(splitContainer.Panel, ee); };
The first two lambdas set the effect to copy without any checks; of course you will want to add those and pick the appropriate effect.
The third lambda passes the DragEventArgs on the the DragDrop event of the containing panel, so now the RTB is actually 'D&D-through' ..
Just create a custom RichTextBox and override it's DragDrop Events.
public class CustomRichTextBox : RichTextBox
{
#region Methods
#region Overrides
protected override void OnDragEnter(DragEventArgs e)
{
// base.OnDragEnter(e);
}
protected override void OnDragOver(DragEventArgs e)
{
// base.OnDragOver(e);
}
protected override void OnDragLeave(DragEventArgs e)
{
// base.OnDragLeave(e);
}
protected override void OnDrop(DragEventArgs e)
{
// base.OnDrop(e);
}
#endregion
#endregion
}
For some reason RichTextBoxes seem to handle all DragDrop events by default.
In WPF the events will propagate till it gets to the control that expects these events. I'm not sure about WinForms though.
This is what resolved this issue for me.
I had these two events defined, which should have been good enough
MyRichTextBox.DragEnter += MyRichTextBox_DragEnter;
MyRichTextBox.DragDrop += MyRichTextBox_DragDrop;
I found that this one is also apparently needed when using a RichTextBox embedded in
certain controls.
MyRichTextBox.DragOver += MyRichTextBox_DragOver;
private void MyRichTextBox_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}

Disable the row selection on right click in wpf datagrid if row is already selected

I am working on a datagrid which enable user to select multiple rows. but when user clicks on header of row the selection get lost.
How can I disable the row selection on right click if the row is already selected.
I tried to do it via Behavior
public class DataGridRowBehavior
{
public static readonly DependencyProperty DisableSelectionOnRightClickProperty = DependencyProperty.RegisterAttached(
"DisableSelectionOnRightClick",
typeof(bool),
typeof(DataGridRowBehavior),
new UIPropertyMetadata(false, OnDisableSelectionOnRightClick));
public static bool GetDisableSelectionOnRightClick(DependencyObject dgRow)
{
return (bool)dgRow.GetValue(DisableSelectionOnRightClickProperty);
}
public static void SetDisableSelectionOnRightClick(DependencyObject dgRow, bool value)
{
dgRow.SetValue(DisableSelectionOnRightClickProperty, value);
}
public static void SetListViewFocus(DependencyObject d, bool use)
{
d.SetValue(DisableSelectionOnRightClickProperty, use);
}
public static void OnDisableSelectionOnRightClick(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGridRowHeader header = d as DataGridRowHeader;
header.MouseRightButtonUp += header_MouseRightButtonUp;
}
static void header_MouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var header = sender as DataGridRowHeader;
if (header.IsRowSelected)
{
if (header.ContextMenu != null)
{
header.ContextMenu.IsOpen = true;
}
e.Handled = true;
}
}
}
But this one is not working correctly as other functionality of right click is also broken. e.g Context menu. The context menu is not enabling its Application Commands.
Is there any other way to disable the selection or let the selection remain as it is if I click on any of the selected row?
You have two choices:
Either create custom DataGrid or DataGridRow and create SelectionChanging or Selection events. It's needed to prevent selection. This time, this controls have only SelectionChanged and Selected events. Next time, I think you could write code
If you do not want create custom controls you can create a Behavior. For example:
public class SuppressButtonClickBehavior : Behavior<Button>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.AddHandler(UIElement.PreviewMouseLeftButtonDownEvent,
new RoutedEventHandler(OnPreviewMouseLeftButtonDown),
true);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.RemoveHandler(UIElement.PreviewMouseLeftButtonDownEvent,
new RoutedEventHandler(OnPreviewMouseLeftButtonDown));
}
private void OnPreviewMouseLeftButtonDown(Object sender, RoutedEventArgs e)
{
e.Handled = true;
if (AssociatedObject.Command != null)
{
AssociatedObject.Command.Execute(AssociatedObject.CommandParameter);
}
}
}
If you want, you can make this code more flexible. But you must understand, that you can only set e.Handled to true to prevent selection.

Changing the properties of the active control automatically

Please consider that im a newcomer to c#. After scanning about 700 posts i decided to post one more question:
On my windows form (c#) I have some controls including textboxes, checkboxes and so on.
I want to change the backcolor whenever the controls become active.
I know i could raise 'enter' and 'leave' events for each control to change the corresponding properties but there should be another way.
Simply hook Enter and Leave events - toggling the color in each. Save the last color saved in OnEnter to use in OnLeave
public Form1()
{
InitializeComponent();
var lastColorSaved = Color.Empty;
foreach(Control child in this.Controls)
{
child.Enter += (s, e) =>
{
var control = (Control)s;
lastColorSaved = control.BackColor;
control.BackColor = Color.Red;
};
child.Leave += (s, e) =>
{
((Control)s).BackColor = lastColorSaved;
};
}
}
You customize control classes just like you customize any class, you derive your own class and override the virtual methods. Arbitrarily:
using System;
using System.Drawing;
using System.Windows.Forms;
class MyTextBox : TextBox {
protected override void OnEnter(EventArgs e) {
prevColor = this.BackColor;
this.BackColor = Color.Cornsilk;
base.OnEnter(e);
}
protected override void OnLeave(EventArgs e) {
this.BackColor = prevColor;
base.OnLeave(e);
}
private Color prevColor;
}
Now any MyTextBox you drop on the form will have this behavior without having to implement events. Although there's certainly nothing wrong with using events.
Create a class (eg. ControlColorizer) and in its constructor pass:
1) The backcolor for the 'active control' and save to a internal Color variable
2) a variable length Control array
In the contructor add the same event handler for OnEnter and OnLeave on each control
In the OnEnter event set the backcolor
In the OnLeave event set the standard background color
The advantage is all in the use of the class:
1) Declare a global instance in your form class
2) Initialize in the form contructor after the InitializeComponent.
3) Forget everything else. No other code required
So let me explain everything with code:
This will go in a file called ControlColorizer.cs
public class ControlColorizer
{
private Color _setBColor = SystemColors.Window;
public ControlColor(Color bkg, params Control[] ctls)
{
_setBColor = bkg;
foreach (Control o in ctls)
{
o.Enter += new EventHandler(o_Enter);
o.Leave += new EventHandler(o_Leave);
}
}
private void o_Enter(object sender, EventArgs e)
{
if (sender is Control)
{
Control c = (Control)sender;
c.BackColor = _setBColor;
}
}
private void o_Leave(object sender, EventArgs e)
{
Control c = sender as Control;
c.BackColor = SystemColors.Window;
}
Now, in every form contructor where you need the functionality you have this
ControlColirizer _ccz;
public Form1()
{
InitializeComponent();
// Create an instance of ControlColorizer, pass the background color
// the list of Controls and that's all
_ccz = new ControlColorizer(Color.LightYellow, this.TextBox1,
this.TextBox2, this.TextBox3, this.TextBox4);
}

Categories