How to acces a owner of programmatically created Flyout? - c#

I want to acces a owner of flyout which i created.
I have code:
public void dosomething(Grid lessonGrid)
{
var invisibleButton = new Button();
lessonGrid.Children.Add(invisibleButton);
var contentGrid = new Grid()
var buttonInFlyOut = new Button { Content="Click" };
buttonInFlyOut.Click += buttonClicked;
contentGrid.Children.Add(buttonInFlyOut);
var flyout = new FlyoutForLessons {
Content = contentGrid
};
flyout.Closed += (f, h) =>
{
lessonGrid.Children.Remove(invisibleButton);
};
flyout.Owner = lessonGrid;
flyout.ShowAt(invisibleButton); // i want to acces a owner from parent of invisible Button -> lessonGrid
}
private class FlyoutForLessons : Flyout
{
private static readonly DependencyProperty OwnerOfThisFlyOutProperty = DependencyProperty.Register(
"owner", typeof(UIElement), typeof(FlyoutForLessons),
null);
public UIElement Owner
{
get { return (UIElement) GetValue(OwnerOfThisFlyOutProperty); }
set { SetValue(OwnerOfThisFlyOutProperty, value); }
}
}
This code shows me a flyout. So when i click a button "buttonInFlyOut" i want to get id of "lessonGrid" from sender in this method:
private void buttonClicked_Click(object sender, RoutedEventArgs e)
{
}
As you see, i tried to create a new Flyout with custom property, but i can't get this flyout from sender at method above. I don't know how to do it, and i don't want to create a private static variable which keeps an instance of grid where flyout's appears.
If live tree helps:

There's no reason why you couldn't handle your click event like this:
public void DoSomething(Grid lessonGrid)
{
var invisibleButton = new Button();
lessonGrid.Children.Add(invisibleButton);
var contentGrid = new Grid();
var buttonInFlyOut = new Button { Content = "Click" };
buttonInFlyOut.Click += (o, args) =>
{
this.OnButtonClicked(lessonGrid);
};
contentGrid.Children.Add(buttonInFlyOut);
var flyout = new Flyout { Content = contentGrid };
flyout.Closed += (f, h) => { lessonGrid.Children.Remove(invisibleButton); };
flyout.ShowAt(invisibleButton); // i want to acces a owner from parent of invisible Button -> lessonGrid
}
private void OnButtonClicked(Grid lessonGrid)
{
// Do something here
}
This allows you to access the grid that you passed in to the method.
Due to the way that Flyout isn't a FrameworkElement, you'll never find it in the visual tree which is why you see the pop up in your screenshot is outside of the frame. Without either setting a property that you access in your method or trying it in the way I've described above, I don't think it's possible to do this.

Related

C# WinForms - Cannot access a control in a handler method

I have a form containing two flow layout panels (FLP), which dynamically have buttons added to them. These buttons are actually a class called tagButton which inherits from Button and I have added a handler in the constructor for the click() method. On click, I want to remove the button from the FLP it is currently in then add it to the other FLP.
Below is a trimmed down version of my code for the tagButton class. Note that the tagButton class is defined inside the of the form class both FLPs are in:
class tagButton : Button
{
public string tag = "";
public bool useTag = false; //tells you which FLP the button is in
public tagButton(String tag, Boolean useTag)
{
this.tag = tag;
this.Text = tag;
this.useTag = useTag;
this.Click += TagButton_Click;
}
private void TagButton_Click(object sender, EventArgs e)
{
tagButton tagButton = (tagButton)sender;
tagButton.useTag = !tagButton.useTag;
if (tagButton.useTag)
{
flowLayoutPanel.Controls.Remove(tagButton);
}
}
}
I'm having problems with the last line:
flowLayoutPanel.Controls.Remove(tagButton);
I can switch it to the following and it works, however there is no way for me to add it to the other FLP. Or at least, not without doing Parent.Parent.Parent.Controls[1]... etc which is clearly a bad idea.
tagButton.Parent.Controls.Remove(tagButton);
I've tried switching different classes and methods to static but nothing I tried worked, the this keyword doesn't seem to work either.
I would recommend having a separate class overriding a parent control that's aware of both FlowLayoutPanels. Then, when your button wants to switch, it can find that custom control in its parents and invoke a custom "switch" function that would move the invoking button from the list it's in to the list it wasn't in.
One of many ways to achieve this outcome is to have MainForm expose a static array of the FlowLayoutPanel candidates as Panels property:
public partial class MainForm : Form
{
public static Control[] Panels { get; private set; }
char _id = (char)64;
public MainForm()
{
InitializeComponent();
Panels = new Control[]{ flowLayoutPanelLeft, flowLayoutPanelRight, };
buttonAddLeft.Click += (sender, e) =>
{
flowLayoutPanelLeft.Controls.Add(new tagButton
{
Height= 50, Width=150,
Name = $"tagButton{++_id}",
Text = $"Button {_id}",
});
};
buttonAddRight.Click += (sender, e) =>
{
flowLayoutPanelRight.Controls.Add(new tagButton
{
Height= 50, Width=150,
Name = $"tagButton{++_id}",
Text = $"Button {_id}",
});
};
}
}
Then, suppose you want to swap between panels when a tagButton gets a right-click (for example).
class tagButton : Button
{
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (MouseButtons.Equals(MouseButtons.Right))
{
Control dest;
if(Parent.Name.Contains("Left"))
{
dest = MainForm.Panels.First(_=>_.Name.Contains("Right"));
}
else
{
dest = MainForm.Panels.First(_ => _.Name.Contains("Left"));
}
Parent.Controls.Remove(this);
dest.Controls.Add(this);
}
}
}

WPF Custom control with ContextMenu ItemCollection

I'm trying to create something like expandable button - button with context menu above, which will open by left mouse button click. Only thing I still can't finishing up is items property, which could be setting up in WPF XAML designer like for ContextMenu control. So, as far as I can understand, that means I need to use ItemCollection type for my property. Ok. Let's take a look on my component:
public partial class MenuButton : Button
{
private readonly ContextMenu Menu;
public static readonly DependencyProperty MenuItemsProperty =
DependencyProperty.Register("MenuItems", typeof(ItemCollection), typeof(ContextMenu), new PropertyMetadata(default(ItemCollection), new PropertyChangedCallback(OnSomeMenuItemsPropertyChanged)));
public ItemCollection MenuItems
{
get => (ItemCollection)GetValue(MenuItemsProperty);
set => SetValue(MenuItemsProperty, value);
}
public MenuButton()
{
MenuItems = new DataGrid().Items; // I really need to do it - otherwise I'll get an error
Menu = new ContextMenu
{
HasDropShadow = false,
PlacementTarget = this,
Placement = PlacementMode.Top,
};
InitializeComponent();
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Menu.Width = ActualWidth;
Menu.IsOpen = true;
}
private static void OnSomeMenuItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
if (target is ContextMenu menu)
{
menu.ItemsSource = (ItemCollection)e.NewValue;
}
}
}
But I can't 'catch' the OnSomeMenuItemsPropertyChanged - breakpoint does not work here. So, that means this mechanism is wrong.
How can I fix that? Should I use OnItemsChanged and OnItemsCollectionChanged events instead (like for ObservableCollection) to handle the changes of the property? Or maybe something else?
So, finally I found the solution. I don't claim that this is the best practice, but maybe it will be useful for someone:
public partial class MenuButton : Button
{
private readonly ContextMenu Menu;
public static readonly DependencyProperty MenuItemsProperty =
DependencyProperty.Register("MenuItems", typeof(ItemCollection), typeof(MenuButton), new PropertyMetadata(default(ItemCollection), new PropertyChangedCallback(OnSomeMenuItemsPropertyChanged)));
public ItemCollection MenuItems
{
get => (ItemCollection)GetValue(MenuItemsProperty);
set => SetValue(MenuItemsProperty, value);
}
public MenuButton()
{
Menu = new ContextMenu
{
HasDropShadow = false,
PlacementTarget = this,
Placement = PlacementMode.Top,
};
MenuItems = new DataGrid().Items;
InitializeComponent();
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Menu.Width = ActualWidth;
Menu.IsOpen = true;
}
private static void OnSomeMenuItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var menuButton = target as MenuButton;
menuButton.Menu.ItemsSource = (ItemCollection)e.NewValue;
}
}

How do you create an OnClick command in WPF MVVM with a programmatically created button?

I am writing a WPF application that programmatically creates a few buttons. How do you create an OnClick command for a button in the ViewModel? I would like to add a command to clear all TextBoxes with the ResetButton.
new StackPanel
{
Orientation = Orientation.Horizontal,
Children =
{
new Button { Name = "SendButton", Content = "Send", MinWidth = 50, MaxHeight = 30, Margin = new Thickness(5), Background = Brushes.DodgerBlue },
new Button { Name = "ResetButton", Content = "Reset", MinWidth = 50, MaxHeight = 30, Margin = new Thickness(5), Background = Brushes.DarkRed}
}
});
Do you have access to the view model as you are creating the Stack Panel?
If so, you have your View Model expose a Command:
var myViewModel = (MyViewModel)this.DataContext;
Button sendButton = new Button
{
Name = "SendButton",
Command = myViewModel.SendCommand,
// etcd
}
And in your view model:
class MyViewModel : INotifyPropertyChanged
{
private class SendCommand : ICommand
{
private readonly MyViewModel _viewModel;
public SendCommand(MyViewModel viewModel)
{
this._viewModel = viewModel;
}
void ICommand.Execute(object parameter)
{
_viewModel.Send();
}
bool ICommand.CanExecute(object p)
{
// Could ask the view nodel if it is able to execute
// the command at this moment
return true;
}
}
public ICommand SendCommand
{
get
{
return new SendCommand(this);
}
}
internal void Send()
{
// Invoked by your command class
}
}
This example creates a new class just for this one command. After you've done this more than once you'll probably see a pattern, and wrap it up in a generic utility class. See http://www.wpftutorial.net/delegatecommand.html for an example, or use any of the myriad of WPF extension libraries.
Answer to your first question:
How do you create an OnClick command for a button in the ViewModel?
You can actually do this to add onclick for a button:
Button button = new Button { Name = "ResetButton"};
button.Click += button_Click; (button_Click is the name of method)
void button_Click(object sender, RoutedEventArgs e)
{
//do what you want to do when the button is pressed
}
by the way, Andrew's solution is better. ooaps.

Closing a Window from its content's ViewModel in MVVM WPF?

Creating a Window like so, using my custom UserControl as the content:
Window newCacheForm = new Window
{
Title = "Add New Cache Tag",
Content = new NewCacheControl()
};
I want to open the Window as a dialog and get the result:
var result = newCacheForm.ShowDialog();
I have the code in place to bind and set the dialog to true or false, but how do I close the Window from the UserControl ViewModel? If that can't be done, how do I work this in an MVVM friendly way?
In this case I would use an attached behavior, it allows using independent logic on the side of View. I personally did not create it, but took here and little supplemented - added Get() to a dependency property.
Below as a full code of this behavior:
public static class WindowCloseBehaviour
{
public static bool GetClose(DependencyObject target)
{
return (bool)target.GetValue(CloseProperty);
}
public static void SetClose(DependencyObject target, bool value)
{
target.SetValue(CloseProperty, value);
}
public static readonly DependencyProperty CloseProperty = DependencyProperty.RegisterAttached("Close",
typeof(bool),
typeof(WindowCloseBehaviour),
new UIPropertyMetadata(false, OnClose));
private static void OnClose(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue))
{
Window window = GetWindow(sender);
if (window != null)
window.Close();
}
}
private static Window GetWindow(DependencyObject sender)
{
Window window = null;
if (sender is Window)
window = (Window)sender;
if (window == null)
window = Window.GetWindow(sender);
return window;
}
}
In the Click handler of creating new Window I added this behavior like this:
private void Create_Click(object sender, RoutedEventArgs e)
{
Window newCacheForm = new Window
{
Title = "Add New Cache Tag",
Content = new TestUserControl(),
DataContext = new TestModel() // set the DataContext
};
var myBinding = new Binding(); // create a Binding
myBinding.Path = new PropertyPath("IsClose"); // with property IsClose from DataContext
newCacheForm.SetBinding(WindowCloseBehaviour.CloseProperty, myBinding); // for attached behavior
var result = newCacheForm.ShowDialog();
if (result == false)
{
MessageBox.Show("Close Window!");
}
}
And in the Close handler of UserControl write this:
private void Close_Click(object sender, RoutedEventArgs e)
{
TestModel testModel = this.DataContext as TestModel;
testModel.IsClose = true;
}
Naturally, instead of Click handlers for the Buttons should be used the commands.
The entire project is available here.

How can I change label text in different class (C#)

Please, you can you help me, how can i change label text in diferent class?
Basic winform script:
public partial class buildEditor : Form
{
public buildEditor()
{
InitializeComponent();
Label maxSkillPoint = new Label();
maxSkillPoint.AutoSize = true;
maxSkillPoint.BackColor = System.Drawing.Color.Transparent;
maxSkillPoint.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
maxSkillPoint.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(193)))), ((int)(((byte)(196)))), ((int)(((byte)(181)))));
maxSkillPoint.Location = new System.Drawing.Point(528, 687);
maxSkillPoint.Name = "maxSkillPoint";
maxSkillPoint.Text = UniqueValue.spentSkillPoints.ToString();
maxSkillPoint.Size = new System.Drawing.Size(0, 20);
this.Controls.Add(maxSkillPoint);
}
public void maxSkillPoint_TextChanged(Form formInstance, string labelName)
{
// Get reference to the label
var label = formInstance.Controls.Find(labelName, true).FirstOrDefault();
if (null != label && label is Label)
{
(label as Label).Text = "test";
}
}
}
I created next class which will be change text for maxSkill text.
public class ChangeTextForMaxSkill()
{
Button button = new Button();
public ChangeTextForMaxSkill()
{
button.Click += new EventHandler(changeText);
}
private void changeText(object sender, EventArgs e)
{
// Get reference to the label
var buildEditor = new buildEditor();
buildEditor.maxSkillPoint_TextChanged(buildEditor, "maxSkillPoint");
}
}
I really thx for all answers.
I got it very simple:
Hand over the Label control in the constructor of your external class:
using System.Windows.Forms;
public class Yourclass{
private Label UpdateLabel;
public Yourclass (Label yourLabel)
{
this.UpdateLabel = yourlabel;
}
private void action()
{
//here is your update of the label
UpdateLabel.Text = "Your text";
}
}
In the form class, create an instance of "yourclass" and hand over the Label:
Yourclass cls = new Yourclass(Label1);
First of all your naming conventions do not follow standard practices. Both class and method names should use uppercase first letters of words, not camel case as you have done. I have used proper naming conventions in my answer.
You have to pass an instance of your BuildEditor* form to your ChangeTextForMaxSkill.ChangeText() function.
Next, the label object maxSkill is not a property of your BuildEditor class. Therefore, you'd need to actually find a reference to that control within the form since you're dynamically adding it.
public partial class BuildEditor : Form
{
public BuildEditor()
{
InitializeComponent();
Label maxSkill = new Label();
maxSkill.Name = "MaxSkil"; // need the ID to find it later (makes it easier)
maxSkill.Location = new Point(1, 1);
this.Controls.Add(maxSkill);
}
// This is just for testing; assumes you dragged a button from toolbox onto your
// BuildEditor form in the Form Designer
private void button1_Click(object sender, EventArgs e)
{
var changeTextForMaxSkill = new ChangeTextForMaxSkill();
changeTextForMaxSkill.ChangeText(this, "MaxSkil");
}
}
public class ChangeTextForMaxSkill
{
public void ChangeText(Form formInstance, string labelName)
{
// Get reference to the label
var label = formInstance.Controls.Find(labelName, true).FirstOrDefault();
if (null != label && label is Label)
{
(label as Label) .Text = "test";
}
}
}
If you want to test it, just add a button on your form and make the test in the button click handler:
private void button1_Click(object sender, EventArgs e)
{
var changeTextForMaxSkill = new ChangeTextForMaxSkill();
changeTextForMaxSkill.ChangeText(this, "MaxSkil");
}
I've tested and this works :)

Categories