Getting frame from custom WPF user control - c#

I am developing a WinRT 8.1 application and I have a MenuFlyout within my custom control. Essentially, when a user clicks an Item within the MenuFlyout, the user is navigated to a different page. My dilemma is that I cannot access the Page element within my user control. Is there any work-around for this? I have looked at many similar SO questions, but none of them worked for me.
public sealed partial class BottomAppBar : UserControl {
public BottomAppBar() {
this.InitializeComponent();
//we are forced to manually add items as flyout does not support command
foreach (Vault v in User.Instance.Vaults) {
MenuFlyoutItem vault = new MenuFlyoutItem();
vault.Text = v.Name;
vault.Click += switchUser;
flyoutVault.Items.Add(vault);
}
}
private void switchUser(object sender, object e) {
//This line results in an error
this.Frame.Navigate(typeof(LoginPage));
/** Does not work as well
var parent = VisualTreeHelper.GetParent(this);
while (!(parent is Page)) {
parent = VisualTreeHelper.GetParent(parent);
}
(parent as Page).Frame.Navigate(typeof(LoginPage));
*/
}

The design-patterned solution is to create a navigation service passing the app frame to it and then use something like dependency injection to pass the navigation service to whomever might need it.
The simple solution is to store the reference to the Frame in your App class and access it through the app object/static property.

Related

C# WPF Affecting main thread from UserControl class

I am trying to make a simple image viewer that loads images at start, creates Items with pulled SQL data and then creates Dictionary with all the Item parameters.
Then another method builds what's needed and fills StackPanel with UserControls (ITEM) that act as "links" to images.
Trying to add another image, from UserControl (NEW_ITEM) nested in View that is loaded to ContentControl in MainWindow.
public partial class fileUpload : UserControl
{
public fileUpload()
{
InitializeComponent();
}
string PathSource = #"SOURCE";
string PathToCopy = #"DESTINATION";
private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
PathSource = FU_source.Text;
if (!FU_fileName.Text.Contains("."))
{
MainWindow V = new MainWindow();
V.LoadCollection(); //This seems to work, but the content on MainWindow stays visible.
////Copy Inserted Item
File.Copy(PathSource,PathToCopy + FU_fileName.Text + ".png", true);
////Remove Item Preview From List
((ItemsControl)this.Parent).Items.Remove(this);
}
else
{
MessageBox.Show("ERR TEST", "ERR TEST HEADER");
}
}
}
Running LoadCollection() method does work OK if it's called from the MainWindow but does not work if called from fileUpload Class. (Program seems to do what's needed, but the user control won't disappear.)
Basically, it should refresh the whole process and refresh/refill StackPanel with new items.
I have encountered this problem many times and I think that I'm missing a key part in this.
TL;DR: I am trying to refresh MainWindows StackPanel with new Items, but the controls won't disappear if method taking care of this is called from UC Class.

How to add an existing control in another on properties panel at design time using C# WindowsFormApplication

I have custom controls: CustomControlOne, CustomControlTwo.
My CustomControlOne has a List<CustomControlTwo> showed in your properties panel of my windows form application project:
So when I click in the button, a window to add new items in this collection is opened:
But, I want add existing CustomControlTwo items that are defined in MyForm. ps.: MyForm contains CustomControlOne and multiple CustomControlTwo.
I want this items can be added in design time, like same way that selecting an item in a comboBox (in CustomControlOne properties panel). When I change List<CustomControlTwo> to ICollection<CustomControlTwo> a comboBox is showed but the items of type CustomControlTwo not appears :(
How I can do this ?
How I can say to my CustomControlOne where are the CustomControlTwo items ?
Thx.
UPDATE:
I wrote my own UITypeEditor and I reached my goal with help of #Sefe and with THIS link. Bellow my code:
public class CollectionTypeEditor : UITypeEditor {
private IWindowsFormsEditorService _editorService = null;
private ICollection<Control> mControls = null;
private List<Control> mPickedControls = null;
// Editor like Modal style
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
return UITypeEditorEditStyle.Modal;
}
// Opens modal and get returned data
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
if (provider == null)
return value;
_editorService = (IWindowsFormsEditorService) provider
.GetService(typeof(IWindowsFormsEditorService));
if (_editorService == null)
return value;
mControls = new List<Control>();
// retrieve old data
mPickedControls = value as List<Control>;
if (mPickedControls == null)
mPickedControls = new List<Control>();
// getting existent controls that will be inflated in modal
Control mContext = (Control) context.Instance;
GetControls(mContext);
// open form and get response
CollectionDesign<Control> frmCollections = new CollectionDesign<Control>(mControls, ref mPickedControls);
var response = _editorService.ShowDialog(frmCollections);
// returning data from editor
return response == DialogResult.OK ? mPickedControls : value;
}
Everything works well here. Now, my variable in properties panel:
[Editor(typeof(CollectionTypeEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ActionButtonConverter))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<Control> ActionButtons { get; set; }
The serialization attribute was added because the file couldn't be saved. When form is closed and reopened, all data are lost.
The stranger thing is that I wrote other UITypeEditor like the same way, just changing type of data to string and I can close or reopen my form and all works fine, the data are saved.
I already added a TypeConverter but I think that isn't case here. what is wrong with my code?
My basic setup:
In this setup, BaseForm extends Form. With string works, but with List data are lost on close form or build project...
Resumed workflow:
1 - Open MyForm.
2 - Click at ActionButtons ellipsis [...] (inherited by BaseForm) on MyForm properties panel.
3 - A custom form is opened with inflated objects that I want pick.
4 - Objects that I want are picked and I close form. So now, data is ok cause I can reopen that form and see objects that I picked.
5 - Now when close the MyForm and reopen it, all data are lost. The same thing happens when build the project. But if I do all this steps with a string, all are Ok (data are saved).
Thanks all, and sorry for bad language :P
If you want to add multiple CustomControlTwo items to your control, a drop down list will do you no good, since you can only select one value there.
If you are able to change the property to accept a single instance of CustomControlTwo (or are OK that the list you can create in your property browser has only one item), you can create a new System.ComponentModel.TypeConverter that will create the list to select from. A type converter is an abstract class and you will have to implement a couple of abstract methods. The methods that are valuable to you are GetStandardValuesSupported, GetStandardValuesExclusive and GetStandardValues. Here is the part that is interesting to you (you have to add the other methods):
public class CustomControlOneConverter : TypeConverter {
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
//By returning true, you tell the property designer to add a drop down list
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
//By returning true, you tell the property designer to not allow the user to enter his own text
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
//Get all objects of type CustomControlTwo from the container
CustomControlTwo[] controlsToList =
context.Container.Components.OfType<CustomControlTwo>().ToArray;
//Return a collection of the controls
return new StandardValuesCollection(controlsToList);
}
//implement the other abstract methods
}
What you do in GetStandardValues is to search in the container of CustomControlOne, if there are instances of CustomControlTwo. Those are then added to the list of standard values to select from in the property browser.
If you are not able to change your property to select only one CustomControlTwo, you will need to create your own UI to display when the ellipsis (...) is clicked in your property window. For that you need to create your own UITypeEditor. That, however is a more complex undertaking and is not easily explained in a nutshell.

Caliburn Micro Screen Activated Events

I'm using Caliburn Micro and AvalonDock in my project. I try to handle events when screen was activated. My main view model is a 'Conductor.Collection.OneActive' and each tab "document" is a 'Screen'.
I have a function in my main view model like this:
public void CheckAndRegisterDocument(DocumentViewModel document)
{
DocumentViewModel exists = _documents.FirstOrDefault((d) => { return d.Equals(document); });
// if not exists - add it
if(exists == null) {
document.Activated += Document_Activated;
_documents.Add(document);
Items.Add(document);
}
// activate and set property object
ActivateItem(document);
Properties.PropertiesObject = document.Properties;
}
// document activated event handler
private void Document_Activated(object sender, ActivationEventArgs e) {
ActiveDocument = sender as DocumentViewModel;
}
But the function "Document_Activated" is not invoked. what am I doing wrong?
Instead of adding your document objects into a documents collection, add them to the already existing this.Items collection.
Also, each document object will need to inherit from Screen for it to participate.
That +should+ be enough to do the trick, but sometimes it can be necessary to tell Caliburn to "conduct" your viewmodels via ConductWith...
document.ConductWith(this)
there this is the current conductor viewmodel.

Container UserControl - Handle and Modify Added Controls

I'm trying to create a custom container as UserControl.
My Goal: I want to be able to drag controls inside the designer and handle incoming controls inside the code of my usercontrol.
Example: I place my container somewhere and then add a button. In this momemt I want my usercontrol to automatically adjust the width and position of this button. Thats the point where Im stuck.
My code:
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
public partial class ContactList : UserControl
{
public ContactList()
{
InitializeComponent();
}
private void ContactList_ControlAdded(object sender, ControlEventArgs e)
{
e.Control.Width = 200; // Nothing happens
e.Control.Height = 100; // Nothing happens
MessageBox.Show("Test"); // Firing when adding a control
}
}
The MessageBox is working well. The set width and height is ignored.
The question is just "why?".
EDIT
I've just noticed, when placing the button and recompiling with F6 the button gets resized to 200x100. Why isnt this working when placing?
I mean... the FlowLayoutPanel handles added controls right when you place it. Thats the exact behaviour im looking for.
Using OnControlAdded
To fix your code, when you drop a control on container and you want to set some properties in OnControlAdded you should set properties using BeginInvoke, this way the size of control will change but the size handles don't update. Then to update the designer, you should notify the designer about changing size of the control, using IComponentChangeService.OnComponentChanged.
Following code executes only when you add a control to the container. After that, it respects to the size which you set for the control using size grab handles. It's suitable for initialization at design-time.
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
if (this.IsHandleCreated)
{
base.BeginInvoke(new Action(() =>
{
e.Control.Size = new Size(100, 100);
var svc = this.GetService(typeof(IComponentChangeService))
as IComponentChangeService;
if (svc != null)
svc.OnComponentChanged(e.Control,
TypeDescriptor.GetProperties(e.Control)["Size"], null, null);
}));
}
}

Should code be inside user control or the form using it

I have a small design question which I couldn't find relevant google hits for some reason.
I have a user control which I use in my application.
The main form opens a second form as a dialog. T
his second form is using the user control which includes a list box.
Naturally I want to preserve the list box items when the forms dispose so I am keeping a private list in the main form.
List<string> _listofFirstCoordinates = new List<string>();
Now the question is, should the dialog form be the one responsible for relaying the list to the main form or should the code be in the user control?
Should the one populating the list be the user control
lst_Coordinates.Items.AddRange(ListOfCoordinates.Cast<object>().ToArray());
or should the form using it populate it (The subform)
uc_EditCoordinates.ListOfCoordinates = ListOfCoordinates;
Also is it feasible to just have the user control be a public variable for the form holding it so it may be changed directly or would that be bad design?
Edit:
By the way, the data is saved for now in variables going back and forth between the forms as the user has to finish all subforms before submitting and finally saving it to the database. So it is a
var _listofFirstCoordinates = new List<string>();
going back and forth.
The "correct" solution is to abstract-away the View-level concern (in this case, anything to do with Form, UserControl, and UI controls) away from the Controller and Model-level concerns (in this case, your application's data).
Without completely rearchitecturing your system, you can still apply this separation-of-concerns within your example.
You can conceptually argue the "code-behind" of your MainForm class acts as a kind of Controller (purists would disagree). It will have to know about creating the child form, but it does not need to know about the user-control hosted within the child form - that would be the concern of the child form's.
I suggest defining a class that represents a ViewModel - albeit as we're using WinForms we will use it as a kind of crude "one-way" ViewModel, like so:
class MainForm : Form {
private void ShowChildFormModal() {
ChildViewModel vm = new ChildViewModel();
vm.CoordinatesList = ...
vm.OtherData = ...
ChildForm child = new ChildForm();
child.LoadFromViewModel( vm );
child.ShowDialog();
child.SaveToViewModel( vm );
SaveToDatabase( vm );
}
}
class ChildViewModel { // this is a POCO
public List<String> CoordinatesList;
public Int32 OtherData;
}
class ChildForm : Form {
public void LoadFromViewModel(ChildViewModel vm) {
// save time and trouble by using the List as a datasource directly, or you can manually populate the combobox as well
this.childUserControl.LoadFromViewModel( vm );
this.someOtherControl.Value = vm.OtherData;
}
public void SaveToViewModel(ChildViewModel vm) {
// completing this is an exercise for the reader
// but basically copy values from the controls on the form into the `vm` instance
}
}
class ChildUserControl : UserControl {
public void LoadFromViewModel(ChildViewModel vm) {
this.comboBox.DataSource = vm.CoordinatesList;
}
}

Categories

Resources