change main form controls by trigger event inside canvas - wpf

I am currently working on a project that required me to use a canvas in order to draw rectangles around specific places in a picture (to mark places)
Each rectangle (actually "rectangle" since it is also a custom class that I created by inheriting from the Grid class and contain a rectangle object) contains properties and data about the marked place inside the picture.
my main form contains controls such as TextBox ,DropDownLists and etc.
Now what I am trying to do is that for each time I am clicking on the "rectangle" object the main form controls will be filled with the object data.
I do not have access to those controls from the canvas class.
this code is inside the costume canvas class to add the object into the canvas:
protected override void OnMouseLeftButtonDown( MouseButtonEventArgs e)
{
if(e.ClickCount==2)
{
testTi = new TiTest();
base.OnMouseLeftButtonDown(e);
startPoint = e.GetPosition(this);
testTi.MouseLeftButtonDown += testTi_MouseLeftButtonDown;
Canvas.SetLeft(testTi, e.GetPosition(this).X);
Canvas.SetTop(testTi, e.GetPosition(this).X);
this.Children.Add(testTi);
}
}
and by clicking an object that is placed inside the canvas i want to get the information.
for now just want to make sure i am getting the right object with a simple messagebox
void testTi_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show(sender.GetType().ToString());
}
this is my costume "Rectangle" class
class TiTest:Grid
{
private Label tiNameLabel;
private Rectangle tiRectangle;
private String SomeText = string.Empty;
private String version = "1.0";
private String application = "CRM";
private String CRID = "NNN";
public String SomeText1
{
get { return SomeText; }
set { SomeText = value; }
}
public Rectangle TiRectangle
{
get { return tiRectangle; }
set { tiRectangle = value; }
}
public Label TiNameLabel
{
get { return tiNameLabel; }
set { tiNameLabel = value; }
}
public TiTest()
{
this.SomeText = "Hello World!!";
this.TiNameLabel = new Label
{
Content = "Test Item",
VerticalAlignment = System.Windows.VerticalAlignment.Top,
HorizontalAlignment = System.Windows.HorizontalAlignment.Left
};
TiRectangle = new Rectangle
{
Stroke = Brushes.Red,
StrokeDashArray = new DoubleCollection() { 3 },//Brushes.LightBlue,
StrokeThickness = 2,
Cursor = Cursors.Hand,
Fill = new SolidColorBrush(Color.FromArgb(0, 0, 111, 0))
};
Background= Brushes.Aqua;
Opacity = 0.5;
this.Children.Add(this.tiNameLabel);
this.Children.Add(this.tiRectangle);
}
}
is there any way to access the main form controls from the costume canvas class or by the costume rectangle class?
Thanks in advance

You can have your main window be binded to a singletone ViewModel holding the properties of the rectangles.
ViewModel
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Singletone
private static MainWindowViewModel _instance;
private MainWindowViewModel()
{
}
public static MainWindowViewModel Instance
{
get
{
if (_instance == null)
_instance = new MainWindowViewModel();
return _instance;
}
}
#endregion
#region Properties
private string _someInfo;
public string SomeInfo
{
get
{
return _someInfo;
}
set
{
if (_someInfo != value)
{
_someInfo = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SomeInfo"));
}
}
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
In main window xaml
<TextBox Text="{Binding SomeInfo}"/>
Also set the view model as your main window data context (in main window constructor for exmaple)
this.DataContext = MainWindowViewModel.Instance;
Finally, from where you handle the click event of the rectangles (testTi_MouseLeftButtonDown), access the MainWindowViewModel instance and set it's properties accordingly.
MainWindowViewModel.Instance.SomeInfo = myRectangle.SomeInfo;
This will trigger the PropertyChanged event, which will update your control's on the main window.
If you are not familiar with the MVVM (Model, View. View Model) pattern you can read about it here
Hope this helps

Related

Message box/dialog/pop-up approach in MVVM

I want to find a way to implement different forms of modals in MVVM WPF application. Like dialogs with returning results, message boxes or modal sub-windows with some controls inside.
Can you give me an advice about an efficient and modern approach for it?
I rarely find much use for anything other than a confirmation request. "Do you really want to delete that?" kind of thing.
Things popping up and asking for extra input just aren't super useful in my experience.
To my mind though, what you're doing is splitting your code. There is code up to showing the dialog. There is then code happens if the user clicks OK or Yes or selects a thingummajig in the dialog.
I split these into separate pieces of code. So there is not necessarilly a need to stop code running. It's in a separate method ( or command ) which is only run if the user hits the right button.
My first approach uses a control which itself has no UI. It exists just to get something encapsulated into the view.
namespace UserInput
{
public class ConfirmationRequestor : Control, ICommandSource
{
public bool? ShowConfirmDialog
{
get
{
return (bool?)GetValue(ShowConfirmDialogProperty);
}
set
{
SetValue(ShowConfirmDialogProperty, value);
}
}
public static readonly DependencyProperty ShowConfirmDialogProperty =
DependencyProperty.Register("ShowConfirmDialog",
typeof(bool?),
typeof(ConfirmationRequestor),
new FrameworkPropertyMetadata(null
, new PropertyChangedCallback(ConfirmDialogChanged)
)
{ BindsTwoWayByDefault = true }
);
private static void ConfirmDialogChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool?)e.NewValue != true)
{
return;
}
ConfirmationRequestor cr = (ConfirmationRequestor)d;
Window parent = Window.GetWindow(cr) as Window;
MessageBoxResult result = MessageBox.Show(parent, cr.Message, cr.Caption, cr.MsgBoxButton, cr.MsgBoxImage);
if (result == MessageBoxResult.OK || result == MessageBoxResult.Yes)
{
if (cr.Command != null)
{
cr.Command.Execute(cr.CommandParameter);
}
}
cr.SetValue(ShowConfirmDialogProperty, false);
}
public MessageBoxButton MsgBoxButton
{
get { return (MessageBoxButton)GetValue(MsgBoxButtonProperty); }
set { SetValue(MsgBoxButtonProperty, value); }
}
public static readonly DependencyProperty MsgBoxButtonProperty =
DependencyProperty.Register("MsgBoxButton",
typeof(MessageBoxButton),
typeof(ConfirmationRequestor),
new PropertyMetadata(MessageBoxButton.OK));
public MessageBoxImage MsgBoxImage
{
get { return (MessageBoxImage)GetValue(MsgBoxImageProperty); }
set { SetValue(MsgBoxImageProperty, value); }
}
public static readonly DependencyProperty MsgBoxImageProperty =
DependencyProperty.Register("MsgBoxImage",
typeof(MessageBoxImage),
typeof(ConfirmationRequestor),
new PropertyMetadata(MessageBoxImage.Warning));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption",
typeof(string),
typeof(ConfirmationRequestor),
new PropertyMetadata(string.Empty));
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message",
typeof(string),
typeof(ConfirmationRequestor),
new PropertyMetadata(string.Empty));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ConfirmationRequestor), new UIPropertyMetadata(null));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(ConfirmationRequestor), new UIPropertyMetadata(null));
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(ConfirmationRequestor), new UIPropertyMetadata(null));
}
}
I have a viewmodel designed to go with this which is exposed as a property on my window viewmodel. That viewmodel and the control encapsulate the confirmer functionality.
namespace UserInput
{
public class ConfirmationRequestorVM : INotifyPropertyChanged
{
private string caption;
public string Caption
{
get { return caption; }
set
{
caption = value;
NotifyPropertyChanged();
}
}
private string message;
public string Message
{
get { return message; }
set
{
message = value;
NotifyPropertyChanged();
}
}
private MessageBoxButton msgBoxButton;
public MessageBoxButton MsgBoxButton
{
get { return msgBoxButton; }
set
{
msgBoxButton = value;
NotifyPropertyChanged();
}
}
private MessageBoxImage msgBoxImage;
public MessageBoxImage MsgBoxImage
{
get { return msgBoxImage; }
set
{
msgBoxImage = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Usage
In the relevant view
<input:ConfirmationRequestor
ShowConfirmDialog="{Binding ShowConfirmation, Mode=TwoWay}"
MsgBoxImage="{Binding confirmer.MsgBoxImage}"
MsgBoxButton="{Binding confirmer.MsgBoxButton}"
Message="{Binding confirmer.Message}"
Caption="{Binding confirmer.Caption}"
Command="{Binding OkCommand}"
/>
Most of that is fairly self explanatory.
When ShowConfirmation is set to true, that control will show a messagebox.
If the user clicks OK or Yes then the OkCommand will be executed.
Hence if you need deletion confirmation then you show your messagebox and the actual deletion would be in that OkCommand.
If you want more complicated UI then rather than using a messagebox at all you could show a window from similar control.
Let's call this a DialogueController. This could be rather simpler since we can rely on our own UI and bind commands.
This control would have a dependency property for view Type. This being the type of a usercontrol which needs to be shown.
Another bool dependency property and change handler would control showing the type.
When the showdialog bool becomes true.
A method instantiates a window ( or it could be a popup if you prefer ) instantiates a usercontrol of the type specified in our other DP. Sets the window datacontext to our current datacontext.
You could use getwindow to find the parent window and set that to parent of our new (dialogue) window instance.
Thus sharing the parent window viewmodel as datacontext.
Call showdialog on the window. Or you could just call show.
You then have whatever UI you wrote in your usercontrol shown in a window.
It has access to your parent window viewmodel so it can reference any of your data you need.
And... It's Yes or OK button can bind to whatever command you defined in that parent window viewmodel.
You could also do things like select from a list and bind selectedFoo in your parent window viewmodel as well.
If you don't showmodal then when you change shared properties anything bound in your parent window can get those changes.
Like I said though.
I've not really come across much demand for that sort of thing.
There are also some elephants in the not-dialog room.
A pop up referencing it's parent viewmodel.
And
An expander similarly.
And
Just overlaying a panel on top of everything inside your parent window. This is how I have done editing for data in datagrids in a number of apps.
The key thing I found was the realisation:
You can "just" split your code into code before the dialog. Show the dialog. Then the "doing" aspect of any dialog-like-UI can go in a separate command.
OK, it's not exactly a road to damascus moment. But it simplifies things. I like simple. More likely to work.

OnPropertyChanged wont change when used wth observable collection and single property

Loads the dataGrid and populates the Datagrid a row of 1'
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
update();
//this.DataContext = this;
}
CricketEvent events = new CricketEvent();
private void update()
{
events.updateList(new CricketEvent[1] { new CricketEvent(){Runs="1"} });
DG1.ItemsSource = events.RunsList;
}
private void DG1_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
Window1 windowToOpen = new Window1();
var selectedUser = this.DG1.SelectedItem;
windowToOpen.Show();
}
}
Main class that loads the OnPropertyChanged I have a List property and string property that calls the OnPropertyChanged but I want the individual "Runs" property to be updated on its own rather than the whole collection.
class CricketEvent : INotifyPropertyChanged
{
private ObservableCollection<CricketEvent> runsList;
public string runs { get; set; }
public CricketEvent(string numofRuns) {
this.Runs = numofRuns;
}
public CricketEvent() { }
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<CricketEvent> RunsList
{
get { return this.runsList; }
set
{
if (value != this.runsList)
{
this.runsList = value;
OnPropertyChanged("RunsList");
}
}
}
public string Runs
{
get { return runs; }
set
{
runs = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Runs");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public ObservableCollection<CricketEvent> updateList(CricketEvent []events)
{
runsList = new ObservableCollection<CricketEvent>(events.ToList());
return runsList;
}
}
This is the update window that brings up a text box and should change the "1s" In the previous window to whatever is typed into the textbox
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
CricketEvent events = new CricketEvent();
MainWindow main = new MainWindow();
private void Button_Click(object sender, RoutedEventArgs e)
{
events.updateList(new CricketEvent[1] { new CricketEvent(txt1.Text.ToString()) });
main.DG1.ItemsSource = events.RunsList;
}
The Button_Click event in Window1 does not use the instance of MainWindow that is show - it creates a new Window instance (that is not shown) and adds the updated list to the DG1.ItemsSource property. To solve that, pass the original instance of Window to the created Window1 in constructor and use that.
However, you should review your update strategy (and code style) because there is potential for improvments:
It is not a good idea to create a new collection if you want to update just one property of one item. Observable collections provide change notification, so you dont have to recreate the collection at all.
Instead of assinging the collection in code behind, use databinding to bind the collection to the ItemsSource. DataBinding results in automatic update of GUI elements if the collection or one item of you collection changed.

ObservableCollection gets reset (item count 0) going between 2 views

I am very new to MVVM so hopefully this isnt a hard problem.
Basically I have two Views:-
1. Holds a Data Grid which displays everything inside the ObservableCollection object
2. The second view basically has two text boxes which adds to the ObservableCollection when the user presses OK on the form.
Bascially what I am doing is showing the 2nd view from the 1st view with a button click, button labelled "Add Project"
Then I enter the information I need to add to the ObservableCollection in the 2nd view. When I press OK on the form, It calls a method "AddMProduct" which basically adds a item to the collection inside the ViewModel.
But the problem is by doing this its creating a new ViewModel() object so therefore reinitialise the ObservableCollection. So therefore the collection gets reset back to zero.
So in the MVVM model, how do I basically retain the collection, between the 2 views and the ViewModel?
Thanks
------------------Code---------------
VIEW 1
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnAddProjectGroup_Click(object sender, RoutedEventArgs e)
{
// Open new window here to add a new project group
AddProductGroup dlg = new AddProductGroup();
dlg.ShowDialog();
}
}
VIEW 2
ProductGroupBindable newProduct = new ProductGroupBindable();
ProductsViewModel viewModel = null;
public AddProductGroup()
{
viewModel = new ProductsViewModel();
InitializeComponent();
}
private void btnOK_Click(object sender, RoutedEventArgs e)
{
newProduct.Id = System.Guid.NewGuid();
newProduct.Name = txtName.Text;
newProduct.Description = txtDescription.Text;
viewModel.AddProduct(newProduct);
this.Close();
}
VIEWMODEL
class ProductsViewModel : INotifyPropertyChanged
{
private ObservableCollection<ProductGroupBindable> m_ProductCollection;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public ObservableCollection<ProductGroupBindable> ProductCollection
{
get
{
Test();
return m_ProductCollection;
}
set
{
m_ProductCollection = value;
}
}
private void Test()
{
m_ProductCollection.Add(new ProductGroupBindable { Description = "etc", Name = "test12" });
}
public ProductsViewModel()
{
if (m_ProductCollection == null)
ProductCollection = new ObservableCollection<ProductGroupBindable>();
}
public void AddProduct(ProductGroupBindable newProduct)
{
ProductCollection.Add(newProduct);
NotifyPropertyChanged("ProductCollection");
}
Consider this simple option to fix the problem. Create an overload constructor of the dialog view that accepts ProductsViewModel as parameter. With that you can pass existing viewmodel object from MainWindow to the dialog, hence avoid instantiating new empty viewmodel :
//in MainWindow
private void btnAddProjectGroup_Click(object sender, RoutedEventArgs e)
{
//Open new window here to add a new project group
AddProductGroup dlg = new AddProductGroup(this.viewmodel);
dlg.ShowDialog();
}
//in AddProductGroup :
public AddProductGroup(ProductsViewModel vm)
{
viewModel = vm;
InitializeComponent();
}

Quickest way to hide an array of pictureboxes

I have an array of pictureboxes named from B11 (co-ords 1,1) to B55 (co-ords 5,5). I would like to hide these all on startup (and in the middle of running). I was thinking of making an array of the names manually but would it be the best solution?
If they all have a common parent control, such as a panel or groupbox (or even the form):
Parent.SuspendLayout()
For Each pbox As PictureBox in Parent.Controls.OfType(Of PictureBox)()
pbox.Visible = False
Next pbox
Parent.ResumeLayout()
The Suspend/Resume-Layout() is to avoid flickering as you modify a bunch of controls at once.
You could extend the PictureBox class and use event handling to accomplish this by:
Adding a public property to the form to tell if the picture boxes should be shown or hidden.
Adding an event to the form that is raised when the show/hide picture box property is changed.
Extending the PictureBox class so that it subscribes to the event of the parent form.
Setting the visible property of the extended PictureBox class to the show/hide property of the parent form.
When the show/hide flag is changed on the parent form all of the picture boxes will change their visibility property accordingly.
Form Code:
public partial class PictureBoxForm : Form {
public PictureBoxForm() {
InitializeComponent();
this.pictureBoxesAdd();
}
private void pictureBoxesAdd() {
MyPictureBox mp1 = new MyPictureBox();
mp1.Location = new Point(1, 1);
MyPictureBox mp2 = new MyPictureBox();
mp2.Location = new Point(200, 1);
this.Controls.Add(mp1);
this.Controls.Add(mp2);
}
public event EventHandler PictureBoxShowFlagChanged;
public bool PictureBoxShowFlag {
get { return this.pictureBoxShowFlag; }
set {
if (this.pictureBoxShowFlag != value) {
pictureBoxShowFlag = value;
if (this.PictureBoxShowFlagChanged != null) {
this.PictureBoxShowFlagChanged(this, new EventArgs());
}
}
}
}
private bool pictureBoxShowFlag = true;
private void cmdFlip_Click( object sender, EventArgs e ) {
this.PictureBoxShowFlag = !this.PictureBoxShowFlag;
}
}
Extended PictureBox Code:
public class MyPictureBox : PictureBox {
public MyPictureBox() : base() {
this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.ParentChanged += new EventHandler(MyPictureBox_ParentChanged);
}
private void MyPictureBox_ParentChanged( object sender, EventArgs e ) {
try {
PictureBoxForm pbf = (PictureBoxForm)this.Parent;
this.Visible = pbf.PictureBoxShowFlag;
pbf.PictureBoxShowFlagChanged += new
EventHandler(pbf_PictureBoxShowFlagChanged);
} catch { }
}
private void pbf_PictureBoxShowFlagChanged( object sender, EventArgs e ) {
PictureBoxForm pbf = (PictureBoxForm)sender;
this.Visible = pbf.PictureBoxShowFlag;
}
}
...or just put 'em all on a Panel, and change the panel's visibility.

How do I provide designer support to a TabControl residing in a UserControl, so that I can drag/drop controls onto tab pages?

I have a user control, which contains both a Panel and a TabControl. I enabled design-time support for both. I can drag/drop controls from the toolbox onto the Panel control that resides within the user control. I can also add and remove tab pages via the designer on the TabControl. However, I am not able to drag/drop any controls onto the tab pages themselves.
Below is the code generated source code for my user control:
partial class TestUserControl
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
private void InitializeComponent()
{
this.tabControl = new System.Windows.Forms.TabControl();
this.contentPanel = new System.Windows.Forms.Panel();
this.SuspendLayout();
//
// tabControl
//
this.tabControl.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabControl.Location = new System.Drawing.Point(0, 0);
this.tabControl.Name = "tabControl";
this.tabControl.SelectedIndex = 0;
this.tabControl.Size = new System.Drawing.Size(306, 118);
this.tabControl.TabIndex = 0;
//
// contentPanel
//
this.contentPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
this.contentPanel.Location = new System.Drawing.Point(0, 118);
this.contentPanel.Name = "contentPanel";
this.contentPanel.Size = new System.Drawing.Size(306, 73);
this.contentPanel.TabIndex = 0;
//
// TestUserControl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.tabControl);
this.Controls.Add(this.contentPanel);
this.Name = "TestUserControl";
this.Size = new System.Drawing.Size(306, 191);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TabControl tabControl;
private System.Windows.Forms.Panel contentPanel;
}
Below is the source code I added to enable design-time support:
[Designer(typeof(TestUserControlDesigner))]
public partial class TestUserControl : UserControl
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public TabControl TabControl
{
get { return this.tabControl; }
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Panel ContentPanel
{
get { return this.contentPanel; }
}
public TestUserControl()
{
InitializeComponent();
}
}
internal class TestUserControlDesigner : ParentControlDesigner
{
public override void Initialize(System.ComponentModel.IComponent component)
{
base.Initialize(component);
EnableDesignMode(
(this.Control as TestUserControl).TabControl, "TabControl");
EnableDesignMode(
(this.Control as TestUserControl).ContentPanel, "ContentPanel");
}
}
What do I need to do, so that I can drag/drop controls onto the tab pages of the TabControl?
You have to enable design mode on the existing tab pages as well:
internal class TestUserControlDesigner : ParentControlDesigner {
public override void Initialize(System.ComponentModel.IComponent component) {
base.Initialize(component);
var ctl = (this.Control as TestUserControl).TabControl as TabControl;
EnableDesignMode(ctl, "TabControl");
foreach (TabPage page in ctl.TabPages) EnableDesignMode(page, page.Name);
EnableDesignMode((this.Control as TestUserControl).ContentPanel, "ContentPanel");
}
}

Resources