I have a Visual Studio debugger visualizer project, and when I add a ToolStripComboBox to a ToolStripDropDownMenu, the combobox's items only appear the first time the form is shown.
Like this:
The most basic version of the winform code showing the issue is this:
public class MyVisualizerDialog : Form
{
public MyVisualizerDialog()
{
var toolStripComboBox = new ToolStripComboBox
{
Items = { "One", "Two", "Three" }
};
var toolStripDownDown = new ToolStripDropDownMenu
{
Items = { toolStripComboBox }
};
var toolStrip = new ToolStrip
{
Items =
{
new ToolStripMenuItem("Options")
{
DropDown = toolStripDownDown
}
}
};
Controls.Add(toolStrip);
}
}
Then the visualizer code is simply:
public class MyVisualizer : DialogDebuggerVisualizer
{
protected override void Show(
IDialogVisualizerService windowService,
IVisualizerObjectProvider objectProvider)
{
windowService.ShowDialog(
new MyVisualizerDialog());
}
}
Some extra details:
If I add the ToolStripComboBox to ToolStripMenuItem.DropDownItems, it works fine - it seems to specifically be an issue with having a ToolStripComboBox in a ToolStripDropDown.
If I create and open multiple instances of the same form class in a console app, it works fine.
Once the issue occurs, it keeps occurring - even when I revert the code to the version without the ToolStripDropDown
If I restart Visual Studio, it works the first time the form is shown, then not afterwards.
Any ideas?! Anyone know some wrinkle in the way the IDialogVisualizerService disposes controls or something?!
Thanks for reading :)
It appears that, when the debugger visualizer is closed - which is handled in the debugger side, not in the debuggee side - the DropDown is destroyed but the ToolStripManager doesn't know about it and it finds itself with an invalid handle that it doesn't know how to manage.
Since the ToolStripManager is also active in design mode, this propagates the problem throughout the designer interface: you may find that some DropDown items still work after the debugger visualizer has been closed, but you may not be able to add other ToolStripComboBox items anywhere.
If you insist, also those that appeared to be working may not work anymore.
Note that this misbehavior can translate to ComboBox objects; not directly, but when you try to access their Items collection through the interface.
It may also prevent the Project from compiling.
Explicitly disposing of the Form object created in the debugger visualizer side, can partially solve the problem on the debuggee side, but not, as it turns out, on the debugger visualizer side.
A simple solution is to avoid setting the DropDown object of a ToolStripMenuItem and use a MenuStrip instead, adding Items to a ToolStripDownDown.
Create custom data visualizers
Visualizer Security Considerations
Sample debugger visualizer (simple Image visualizer) to test the good and bad behavior.
▶ Create a Class Library Project, Target Framework set to .Net Framework, AnyCPU profile.
▶ Add a reference to [Visual Studio install Path]\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.DebuggerVisualizers.dll and System.Windows.Forms.
▶ Compile the .dll as Release.
▶ Copy the .dll to the \Common7\Packages\Debugger\Visualizers directory of your current Visual Studio installation path.
▶ Start a debug session, add a breakpoint where an Image/Bitmap property is set/loaded and use the magnifier tool to open a preview.
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.VisualStudio.DebuggerVisualizers;
[assembly: DebuggerVisualizer(
typeof(ImageVisualizer.DebuggerSide),
typeof(VisualizerObjectSource), Target = typeof(Image), Description = "Test Visualizer")]
namespace TestVisualizer
{
public class DebuggerSide : DialogDebuggerVisualizer
{
override protected void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
var image = (Image)objectProvider.GetObject();
var form = new Form();
form.ClientSize = new Size(image.Width, image.Height);
form.FormBorderStyle = FormBorderStyle.FixedSingle;
form.SuspendLayout();
// ------- WORKING CODE ---------------
var menuStrip = new MenuStrip() { };
var tsComboBox = new ToolStripComboBox { Items = { "One", "Two", "Three" } };
var toolStripDownDown = new ToolStripMenuItem() { Text = "Options" };
toolStripDownDown.DropDownItems.AddRange(new ToolStripItem[] { tsComboBox });
menuStrip.Items.AddRange(new ToolStripItem[] { toolStripDownDown });
// ------- WORKING CODE ---------------
// ------- BAD CODE ---------------
//var toolStripComboBox = new ToolStripComboBox { Items = { "One", "Two", "Three" } };
//var toolStripDownDown = new ToolStripDropDownMenu { Items = { toolStripComboBox } };
//var toolStrip = new ToolStrip {
// Items = { new ToolStripMenuItem("Options") { DropDown = toolStripDownDown } }
//};
// ------- BAD CODE ---------------
var pBox = new PictureBox() { Image = image, Dock = DockStyle.Fill };
//form.Controls.Add(toolStrip);
form.Controls.Add(menuStrip);
form.Controls.Add(pBox);
form.MainMenuStrip = menuStrip;
form.ResumeLayout(false);
form.PerformLayout();
windowService.ShowDialog(form);
form?.Dispose();
}
}
}
Related
This question already has answers here:
How to load image to WPF in runtime?
(2 answers)
Closed 1 year ago.
I have a custom control with a default image that I want to change based on which iteration of the control it is. For example, I have one for "F1" and "NumLock" and so on. In the constructor of the control, I have this:
public FixerBox(Dictionary<string,string> deets)
{
InitializeComponent();
btnOff();
this.FixerTitle.Text = deets["Title"];
this.FixerDesc.Text = deets["Description"];
this.FixerTags.Text = deets["Tags"];
this.FixerImg.Source = new BitmapImage(new Uri(deets["Img"], UriKind.Relative));
}
The bitmap stuff was based on another answer and produces this:
Below is the control itself showing that it's correctly getting the title, tags, and description, but the image is bunk (on the left side, that thin grey line is the border that should be around the image).c#
If I was using HTML/CSS, I could right-click the image to see what exactly its properties are, but I don't know how to get that kind of information using WPF. The best I could manage was in the top area is a status window where I've manually printed a "Tostring" output of the first controls image source data. Near as I can tell, it's all correct, but there's no actual image there. Every subsequent control has the same output (one thin line where the image should be).
EDIT Per comments, here is some more of the information. The main XAML file loads up the controls like so in its constructor:
public partial class MainWindow : Window
{
private Fixers fixers = new Fixers();
// This is the custom control consisting mostly of various boxes
private Dictionary<string,FixerBox> fixerBoxes = new Dictionary<string, FixerBox> { };
public MainWindow()
{
InitializeComponent();
var fixNames = fixers.FixerNames();
foreach (string key in fixNames)
{
fixerBoxes[key] = new FixerBox(fixers.GetFix(key));
FixersArea.Children.Add(fixerBoxes[key]);
}
StatusBox.Text += fixerBoxes["F1"].FixerImg.Source.ToString();
}
}
The fixers variable is of class Fixers which consists of the below (abbreviated to show just the F1 function for brevity):
class Fixers
{
private string ClearWS(string str)
{
var first = str.Replace(System.Environment.NewLine, "");
return first.Replace("\t", "");
}
// Loads registry functions
private Regis regStuff = new Regis();
// Loads preferences from the file
private Prefs prefs = new Prefs();
// A timer to make sure the system behaves
private Timer watcher;
// Watcher action toggles
private bool watchNumL = false;
// Translation array from fix shortname to various data about them
private Dictionary<string, Dictionary<string, string>> fixers = new Dictionary<string, Dictionary<string, string>>
{
["F1"] = new Dictionary<string,string> {
["PrefName"] = "KillF1UnhelpfulHelp",
["Img"] = #"/graphics/F1key.png",
["Title"] = #"Diable F1 ""Help"" function",
["Description"] = #"
Have you ever hit the F1 key by accident and had a distracting and unhelpful window or webpage open as a result?
Windows set the F1 key to a generic help function that basically never helps and always gets in the way.
Enable this control to disable that obnoxious design choice. Note that some programs still respond to F1 on their own accord,
but this will stop the default Windows behavior in things like Windows Explorer at least.
",
["Tags"] = "#Keyboard,#Rage"
},
};
public Fixers()
{
// The readability hack above with multi-line strings introduces a bunch of extra whitespace. Let's clear that out
foreach (var fixKey in fixers.Keys)
{
fixers[fixKey]["Description"] = ClearWS(fixers[fixKey]["Description"]);
}
}
public List<string> FixerNames()
{
return fixers.Keys.ToList();
}
public bool IsFixed(string which)
{
// If we're watching, it's fixed
if ("NumL" == which) return watchNumL;
// For anything registry related
return regStuff.IsFixed(which);
}
public Dictionary<string,string> GetFix(string which)
{
return fixers[which];
}
}
if you use binding, you can create in your ViewModel a string, in which is stored the path of your image, then you can easily change programatically its path.
Then in XAML just bind image's source to the string.
In my case I have a list of objects, with the property `ImageName' :
<Image Source="{Binding DataContext.SelectedMacro.ImageName,
RelativeSource={RelativeSource AncestorType=Window}}"/>
I create a desktop app c# where i use some references :
using Microsoft.Toolkit.Uwp.Notifications;
using System.Windows;
using Windows.ApplicationModel.Activation;
using Microsoft.QueryStringDotNET;
And where i added some reference related to UWP application :
- Windows.System
- Windows.UI
- Windows.data
- Windows.Foundation
- Windows.ApplicationModel
Then i created a simple procedure to create and show my toast notification with the followin code :
private void Button_Click(object sender, RoutedEventArgs e)
{
var toastContent = new ToastContent()
{
Visual = new ToastVisual()
{
BindingGeneric = new ToastBindingGeneric()
{
Children =
{
new AdaptiveText()
{
Text = "Surface Launch Party"
},
new AdaptiveText()
{
Text = "Studio S / Ballroom"
},
new AdaptiveText()
{
Text = "4:00 PM, 10/26/2015"
}
}
}
},
Actions = new ToastActionsCustom()
{
Inputs =
{
new ToastSelectionBox("status")
{
DefaultSelectionBoxItemId = "yes",
Items =
{
new ToastSelectionBoxItem("yes", "Going"),
new ToastSelectionBoxItem("maybe", "Maybe"),
new ToastSelectionBoxItem("no", "Decline")
}
}
},
Buttons =
{
new ToastButton("RSVP", "action=rsvpEvent&eventId=63851")
{
ActivationType = ToastActivationType.Foreground
},
new ToastButtonDismiss()
}
},
Launch = "action=viewEvent&eventId=63851"
};
Windows.Data.Xml.Dom.XmlDocument xmldoc = new Windows.Data.Xml.Dom.XmlDocument();
xmldoc.LoadXml(toastContent.GetContent());
var toast = new ToastNotification(xmldoc);
toast.Activated += OnActivated1;
// Create the toast notification
//var toastNotif = new ToastNotification(xmlDoc);
// And send the notification
ToastNotificationManager.CreateToastNotifier("Test").Show(toast);
Now my problem is that i don't know how to retrieve the item i selected in the list :-(
I created an procedure based toast.Activated event :
void OnActivated1(ToastNotification sender, object e)
{
var toastActivationArgs = e as ToastNotificationActivatedEventArgs;
}
With this event, i can retrieve the argument (to know the button i clicked on) but getting the UserInput thanks to the class "ToastNotificationActivatedEventArgs" seems to be not possible...
Do you know if it's possible ? Is it a limitation of using reference UWP in a desktop app ?
Thank you very much !
Vincent
If you're building a Win32 Desktop app using the Desktop Bridge, you currently cannot use inputs and selection boxes in your toast, as there's no way to retrieve the input.
If you're building a normal Win32 app, you must set up a COM server to handle activation, which will include the inputs that the user selected. This quickstart explains how to set this up for normal Win32 apps. Plus, this will also allow your toasts to persist in Action Center, so if the user missed the popup, they can still access your toast from Action Center.
thank you for your answers.
The issue i have with my sub OnActivated :
void OnActivated1(ToastNotification sender, object e)
{
var toastActivationArgs = e as ToastNotificationActivatedEventArgs;
}
is that the "e" object is recocgnized as a Windows.UI.Notifications.ToastActivatedEventsArgs and not as "ToastNotificationActivatedEventArgs" from Windows.ApplicationModel.Activation (where the properties Kind, UserInput.. should be very useful for me to get the content of selected item).
In my OnActivated1 sub, the var toastActivationArgs value equals to null because it cannot be converted to ToastNotificationActivatedEventArgs.
During my test the e.arguments equals to string "action=rsvpEvent&eventId=63851" but there is no XML returned. This is the only property available of the object e. (but this is useful to get the button where i clicked on)
I'm going to check the link from Andrew Bares to try to setup a COM server but i can see that's in c++ language..
Thank you !
The ToastNotificationActivatedEventArgs e.Arguments should be an xml string with what you need ready to parse.
First look at the string and see if it has what you need. Then go about using XMLReader or something to parse it.
Could you post the string you get?
I have a program that has some buttons, one of them is used to switch "Themes".
There are two themes, one is the normal Windows theme and the other is called Style2.
This is how I tried the switching
private bool UsingWindowsStyle = true;
private ResourceDictionary Style2= new ResourceDictionary() { Source = new Uri("/...;component/Resources/DefaultStyles.xaml", UriKind.RelativeOrAbsolute) };
private void SwitchButton_Click(object sender, RoutedEventArgs e)
{
if (UsingWindowsStyle)
{
Resources.MergedDictionaries.Add(Style2);
UsingWindowsStyle = false;
}
else
{
Resources.MergedDictionaries.Remove(Style2);
UsingWindowsStyle = true;
}
}
My problem is, when I use this program, and press this Button, this is what happens:
Window Opened Program operating normally with Windows theme.
SwitchButton First Click Program changes visuals to the Style2 theme. All the program's buttons operating normally.
SwitchButton Second Click Program reverts back to Windows theme, but all the buttons in the program seize to work.
Points to Consider
The program does not throw any exceptions at this point.
Debugging the code, it seems that after the second click, the program does not enter the SwitchButton_Click method.
I tried readding the Click EventHandler but with no use.
SwitchButton.Click += new RoutedEventHandler(SwitchButton_Click);
Thanks in advance for your help.
I would suggest that you are trying too hard. All you need to do is to change the Style on the Window itself. Leave the dictionaries alone. :-)
Here is an example that changes a windows style when you click from the list of available styles.
My command boils down to
//Here I am changing the style on the window
NewWindow.Style = ((StyleDetailsViewModel)x).Style;
NewWindow.Show();
with various input data
public StylingViewModel(Func<string, Style> findStyle)
{
Styles = new StyleDetailsViewModel[]
{
new StyleDetailsViewModel
{
Name = "None",
Description = "Completely remove all styling and show the raw NavigationWindow including default navigation elements",
WindowStyleNone = false,
Image = "\\Resources\\WindowStyleNone.png"
},
new StyleDetailsViewModel
{
Name = "PlainWindow",
Style = findStyle("PlainWindow"),
Description = "Hides the navigation elemetns of the NavigationWindow to make it look just like a normal window",
WindowStyleNone = false,
Image = "\\Resources\\WindowStylePlain.png"
},
new StyleDetailsViewModel
{
Name = "Windows 7",
Style = findStyle("Win7NavigationWindow"),
Description = "Uses glass effects to create a window that looks almost identical to the control panel from Windows 7.",
WindowStyleNone = false,
Image = "\\Resources\\WindowStyleWin7Nav.png"
},
and
this.DataContext = new StylingViewModel(x => (Style)this.FindResource(x));
Also beware of certain Window properties that can only be set before the window opens, such as WindowStyle="None" which you need if you are doing custom chrome.
I'm trying to create a context menu which will be able to add extra menu items, with an attached child menu, as required. I've been trying to do it so I have separate classes building up each part so it can be written nicely with objects.
The problem I am having is that the AddRange method for ContextMenuStrip doesn't have a constructor to deal with my object. I've tried converting it to ToolStripMenuItem type with operators which has not worked, as I suspected it wouldn't.
I am sure this can be achieved so I assume I've thought something through wrong. Is there a way to get around this or I banning my head against a wall with my current structuring?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Context
{
class TestMenu
{
public TestMenu()
{
ContextMenuStrip filesToUploadContext = new System.Windows.Forms.ContextMenuStrip();
// Hot Folder Header
ToolStripMenuItem hotHead = new System.Windows.Forms.ToolStripMenuItem();
// Holder for files in Hot Folder
ParentItem hotFile; // foreach
// Dropped Files Header
ToolStripMenuItem dropHead = new System.Windows.Forms.ToolStripMenuItem();
// Holder for files that have been dragged and dropped in
ParentItem dropFile; // foreach
ToolStripSeparator toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
ToolStripSeparator toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
filesToUploadContext.Items.AddRange(new ToolStripItem[] {
hotHead,
toolStripSeparator1,
hotFile, // Not a toolStrip item
dropHead,
toolStripSeparator2,
dropFile // also not a toolStrip item
});
//// Testing stuff vv
//// Hot Folder
//hotFile.DropDownItems.AddRange(new ToolStripItem[]
// {
// viewHot,
// deleteHotFile
// });
//// Dropped Items Folder
//dropFile.DropDownItems.AddRange(new ToolStripItem[]
// {
// viewDrop,
// removeDropFile
// });
//// Hot Folder Section Heading
//hotHead.Name = "hotHead";
//hotHead.Text = "Hot Folder Files";
//hotHead.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
//// Drop Folder Section Heading
//dropHead.Name = "dropHead";
//dropHead.Text = "Dropped Files";
//dropHead.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
}
class ParentItem
{
// MenuItem to be used for found files
// Options will contain child items
public ToolStripMenuItem name = new ToolStripMenuItem();
public ChildMenu options { get; set; }
public ParentItem();
}
class ChildMenu
{
// Options available for specific files at end of menu tree
public ToolStripMenuItem view = new ToolStripMenuItem("View File");
public ToolStripMenuItem delete = new ToolStripMenuItem("Delete File");
public ToolStripMenuItem remove = new ToolStripMenuItem("Remove File");
public ChildMenu();
}
}
}
You can't add anything else than a ToolStripMenuItem as child of a ToolStripMenuItem.
Don't spend time trying to inherit your ChildMenu and ParentItem classes from ToolStripMenuItem. Just add ToolStripMenuItems and drop these classes.
EDIT:
I would do something like the following (actually compiles):
public void PopulateMenu(string fileName, ContextMenuStrip parent)
{
ToolStripMenuItem newMenu = new ToolStripMenuItem(fileName);
newMenu.DropDownItems.Add(new ToolStripMenuItem("View File"));
newMenu.DropDownItems.Add(new ToolStripMenuItem("Delete File"));
newMenu.DropDownItems.Add(new ToolStripMenuItem("Remove File"));
parent.Items.Add(newMenu);
}
Then for each of your file:
this.PopulateMenu(the_file_name, the_parent_menu);
Also don't forget to attach handlers to the Click event of your menus, or they'll be useless ;-)
In order to convert your object to a different type, like SystemObject newObject = (SystemObject)myObject those two types will need to have some relation to one another. Depending on the circumstance they can either have a common Interface or have some ancestry (if your type inherits from the other type, for example).
In your specific example your object would need to inherit from the ToolStripMenuItem object in order to be converted to one.
I'm sorry it's not better news, I hope that might help you a little though :)
On rereading my answer
I realise I might be teaching you to suck eggs and you're actually looking for a way to use AddRange and an object without a relation to ToolStripMenuItem, so please accept my apologies if that is the case :)
So, I've got a list view, as indicated by the question title. I've got two columns set up: Name and Date Modified. These were added in the designer, here's the code emitted by the designer for reference:
// lstFiles
this.lstFiles.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.clmName,
this.clmDate});
// ...
// clmName
this.clmName.Text = "Name";
this.clmName.Width = 105;
// clmDate
this.clmDate.Text = "Modified";
this.clmDate.Width = 128;
In the designer, this looks beautiful.
The list items themselves are a tiny subclass of ListViewItem that simply extracts some metadata from a file (in this case, the date modified), and adds a sub-item to itself:
class GalleryItem : ListViewItem {
public string File;
public DateTime DateModified;
public GalleryItem(string file) : base(Path.GetFileNameWithoutExtension(file)) {
this.ImageKey = Path.GetExtension(file);
File = file;
DateModified = System.IO.File.GetLastWriteTime(file);
this.SubItems.Add(DateModified.ToString());
}
}
To add items to the list, I simply do this:
lstFiles.BeginUpdate();
lstFiles.Clear();
foreach (String f in files) {
ListViewItem lvi = new GalleryItem(f);
lvi.Group = lstFiles.Groups["grpFiles"]; //this varries
//omitted: check/add icon to list
lstFiles.Items.Add(lvi);
}
lstFiles.EndUpdate();
So, this all works great for Large Icon view, etc:
However, it breaks down on Details view:
There are items in the list (there's a scroll bar). If you click roughly in the column under the red arrow (added in paint), you'll select an item (the upper-right area is an image preview), but you won't see anything selected.
In summary, what am I doing wrong?
I just whipped up a sample to test this:
using System;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var G1 = new ListViewGroup("Group 1");
var G2 = new ListViewGroup("Group 2");
Application.Run(new Form {
Controls = {
new ListView {
Dock = DockStyle.Fill,
Groups = { G1, G2 },
View = View.Details,
//Columns = { "First", "Second" },
Items = {
new ListViewItem { Text = "One", Group = G1, SubItems = { "1" } },
new ListViewItem { Text = "Two", Group = G2, SubItems = { "2" } },
new ListViewItem { Text = "Three", Group = G2, SubItems = { "3" } },
},
},
},
});
}
}
You will notice that it duplicates the problem. If you uncomment the line that creates the columns it works. This suggests that your columns don't exist.
And while typing this the answer popped into my head:
You are calling ListView.Clear instead of ListView.Items.Clear so you are removing the columns in code.
My understanding is that you are not able to use Groups in Details view. I can't test this right now, so I am going off straight memory. However, try populating your list without the Groups and see what happens. I strongly suspect it is the Group part that is causing the problem.
I had exactly the same problem identified by Tergiver, but there is one more gotcha - when you add the columns, the ListView must be in View.Details mode.