This is a 2 parter.
First I am having a heck of a time getting the paste part of a copy and paste operation to work.
I have a method that copies information to the Clipboard, works perfectly.
private void CopyData(string format, object data, string text)
{
bool addedData = false;
DataObject copyData = new DataObject();
if (!string.IsNullOrEmpty(text))
{
copyData.SetData(DataFormats.Text, text);
addedData = true;
}
if (!string.IsNullOrEmpty(format) && data != null)
{
copyData.SetData(format, false, data);
addedData = true;
//this is only for testing
object obj = null;
if (copyData.GetDataPresent(format))
obj = (object)copyData.GetData(format);
}
if (addedData)
Clipboard.SetDataObject(copyData, true);
}
When I check that the data was added the object (obj) is not null.
However when I then go to paste the data from a different method using the same format key I get null, every time.
private void PasteFromClipboard()
{
object obj = null;
IDataObject paste = null;
if (Clipboard.GetDataObject().GetDataPresent("mydatatype"))
obj = (object)Clipboard.GetDataObject().GetData("mydatatype");
else
return;
if (obj == null)
throw new NullReferenceException("Could not gather information from the
}
I have tried everything that I can think of and it just doesn't make sense. I created an array of strings to capture all of the format keys the DataObject was holding and "mydatatype" was the first one. I have tried casting, not casting, using (Clipboard.GetDataObject().GetData("mydatatype") as object) and I just cannot figure it out. I know that there is data there because I can go to NotePad and paste the text that I copied along with the object.
Any thoughts as to why I would be able to get the data in one method, but not another?
Secondly I was wondering how I would go about making a Cut & Paste operation work between two of my windows. I am thinking about something like Excel, where if only the text is pasted the data will remain, however if the objects are pasted then the source will be deleted.
Thanks
Patrick.
Try pulling the data out as Text (instead of "mydatatype") - at least to confirm you can read from the clipboard. This is most likely what Notepad is reading. Also, does it matter that you're copying with "format" but pasting with "mydatatype"?
Could it be that text parameter always has a value and gets set. Then possibly the second if the one that would set the object does not get executed. Or if it does since the data was set in the first if statement the second set fails to set it correctly.
My recommendation would be to walk the code in the debugger during the copy operation.
Before the paste use GetDataObject().GetFormats() to enumerate the list of formatting codes.
Perhaps your using the wrong one.. just an idea
Try using reflection like this:
private static T TryGetClipboardData<T>(IDataObject clipboardData, string dataFormat)
{
System.Reflection.FieldInfo fieldInfo = clipboardData.GetType().GetField("innerData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var outerData = fieldInfo.GetValue(clipboardData);
if (outerData == null)
{
return default(T);
}
fieldInfo = outerData.GetType().GetField("innerData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var innerData = fieldInfo.GetValue(outerData);
if (innerData is System.Runtime.InteropServices.ComTypes.IDataObject)
{
// It is (probably) necessary to wrap COM IDataObject to Windows.Forms.IDataObject
System.Windows.Forms.DataObject wrappedDataObject = new System.Windows.Forms.DataObject(innerData);
var data = wrappedDataObject.GetData(dataFormat);
if (data is T)
{
return (T)data;
}
}
return default(T);
}
I suspect the COM object of your data in the Clipboard had a difficult time converting itself to the format you specified. I'm also playing safe with the input format string so that it is registered as a proper clipboard format.
HTH
Related
I'm writing a visual studio extension based on the Concord Samples Hello World project. The goal is to let the user filter out stack frames by setting a list of search strings. If any of the search strings are in a stack frame, it is omitted.
I've got the filter working for a hardcoded list. That needs to be in a non-package-based dll project in order for the debugger to pick it up. And I have a vsix project that references that dll with an OptionPageGrid to accept the list of strings. But I can't for the life of me find a way to connect them.
On the debugger side, my code looks something like this:
DkmStackWalkFrame[] IDkmCallStackFilter.FilterNextFrame(DkmStackContext stackContext, DkmStackWalkFrame input)
{
if (input == null) // null input frame indicates the end of the call stack. This sample does nothing on end-of-stack.
return null;
if (input.InstructionAddress == null) // error case
return new[] { input };
DkmWorkList workList = DkmWorkList.Create(null);
DkmLanguage language = input.Process.EngineSettings.GetLanguage(new DkmCompilerId());
DkmInspectionContext inspection = DkmInspectionContext.Create(stackContext.InspectionSession, input.RuntimeInstance, input.Thread, 1000,
DkmEvaluationFlags.None, DkmFuncEvalFlags.None, 10, language, null);
string frameName = "";
inspection.GetFrameName(workList, input, DkmVariableInfoFlags.None, result => GotFrameName(result, out frameName));
workList.Execute();
CallstackCollapserDataItem dataItem = CallstackCollapserDataItem.GetInstance(stackContext);
bool omitFrame = false;
foreach (string filterString in dataItem.FilterStrings)
{
if (frameName.Contains(filterString))
{
omitFrame = true;
}
}
The CallstackCollapserDataItem is where I theoretically need to retrieve the strings from user settings. But I don't have access to any services/packages in order to e.g. ask for WritableSettingsStore, like in You've Been Haacked's Example. Nor can I get my OptionPageGrid, like in the MSDN Options Example.
The other thing I tried was based on this StackOverflow question. I overrode the LoadSettingsFromStorage function of my OptionPageGrid and attempted to set a static variable on a public class in the dll project. But if that code existed in the LoadSettingsFromStorage function at all, the settings failed to load without even entering the function. Which felt like voodoo to me. Comment out the line that sets the variable, the breakpoint hits normally, the settings load normally. Restore it, and the function isn't even entered.
I'm at a loss. I really just want to pass a string into my Concord extension, and I really don't care how.
Ok, apparently all I needed to do was post the question here for me to figure out the last little pieces. In my CallstackCollapserDataItem : DkmDataItem class, I added the following code:
private CallstackCollapserDataItem()
{
string registryRoot = DkmGlobalSettings.RegistryRoot;
string propertyPath = "vsix\\CallstackCollapserOptionPageGrid";
string fullKey = "HKEY_CURRENT_USER\\" + registryRoot + "\\ApplicationPrivateSettings\\" + propertyPath;
string savedStringSetting = (string)Registry.GetValue(fullKey, "SearchStrings", "");
string semicolonSeparatedStrings = "";
// The setting resembles "1*System String*Foo;Bar"
if (savedStringSetting != null && savedStringSetting.Length > 0 && savedStringSetting.Split('*').Length == 3)
{
semicolonSeparatedStrings = savedStringSetting.Split('*')[2];
}
}
vsix is the assembly in which CallstackCollapserOptionPageGrid is a DialogPage, and SearchStrings is its public property that's saved out of the options menu.
In Revit 2013 I have tool that I'm making that copies dimensions from one drafting view to another. I've got it to properly create a new version of a dimension including the Curve, DimensionType, and References but I'm having trouble with the properties Above, Below, Prefix, and Suffix. They copy just fine if at least one of them has a value. However, if none of them have a value then it will throw an AccessViolationException when I try to access them. I have tried to catch that exception but it bubbles up and it crashes Revit (I'm assuming it's caused by native code that fails).
How can I check to see if these properties have any value when I do my copying without triggering this AccessViolationException?
Autodesk Discussion Group Question
The DimensionData class is my own used for storing the dimension information so that it can be used to create the dimension in a separate document.
private IEnumerable<DimensionData> GetDimensionDataSet(Document document,
View view)
{
if (document == null)
throw new ArgumentNullException("document");
if (view == null)
throw new ArgumentNullException("view");
List<DimensionData> dimensionDataSet = new List<DimensionData>();
FilteredElementCollector dimensionCollector =
new FilteredElementCollector(document, view.Id);
dimensionCollector.OfClass(typeof(Dimension));
foreach (Dimension oldDimension in dimensionCollector)
{
Line oldDimensionLine = (Line)oldDimension.Curve;
string dimensionTypeName = oldDimension.DimensionType.Name;
List<ElementId> oldReferences = new List<ElementId>();
foreach (Reference oldReference in oldDimension.References)
oldReferences.Add(oldReference.ElementId);
DimensionData dimensionData;
try
{
string prefix = oldDimension.Prefix;
dimensionData = new DimensionData(oldDimensionLine,
oldReferences,
dimensionTypeName,
prefix,
oldDimension.Suffix,
oldDimension.Above,
oldDimension.Below);
}
catch (AccessViolationException)
{
dimensionData = new DimensionData(oldDimensionLine,
oldReferences, dimensionTypeName);
}
dimensionDataSet.Add(dimensionData);
}
return dimensionDataSet;
}
Regarding transactions: As far as I'm aware, you are only required to be inside a transaction when you are making any sort of CHANGE (modifications, deletions, additions). If all you are doing is collecting dimension information, you would not need a transaction, but when you use that information to create new dimensions in another document, that code would have to be inside a transaction. I have had a number of programs under development which did not yet modify the document but simply collected parameter settings and posted them to a TaskDialog.Show(). These programs worked fine, and I don't see anything in your code that actually modifies your model, so that doesn't seem to be your issue.
It seems like I bug.
Can you post the issue to the ADN Support?
The solution I can suggest is to use Parameters of the Dimension element instead of Dimension class properties.
For example, you can get Suffix and Prefix by following code
var suffixParameter =
oldDimension.get_Parameter(BuiltInParameter.SPOT_SLOPE_SUFFIX);
string suffix = null;
if (suffixParameter != null)
{
suffix = suffixParameter.AsString();
}
var prefixParameter =
oldDimension.get_Parameter(BuiltInParameter.SPOT_SLOPE_PREFIX);
string prefix = null;
if (prefixParameter != null)
{
prefix = prefixParameter.AsString();
}
Unfortunatelly, I don't tell you how to get Above and Below Properties via parameters, because I don't have a project to test. But you can easily determine parameters using BuiltInParameter Checker
Hope it helps.
I'm storing some common settings used in a C# program inside the ApplicationSettings. Most of the settings are strings, but one of them is a StringCollection.
A null exception is occurring in some code that loops through the default settings at Properties.Settings.Default and adds them to a dictionary (in preparation to send as parameters), as shown below.
// Generate parameters
Dictionary<string, string> signalparams = new Dictionary<string, string>();
// Add parameters
foreach (SettingsProperty property in Properties.Settings.Default.Properties)
{
SettingsPropertyValue value = new SettingsPropertyValue(property);
if (value.Property.SerializeAs == SettingsSerializeAs.Xml)
{
// Here's where the error occurs
signalparams.Add(value.Name, value.SerializedValue.ToString());
}
else if (value.Property.SerializeAs == SettingsSerializeAs.String)
{
signalparams.Add(value.Name, value.PropertyValue.ToString());
}
}
The strings settings are added fine, but when it reaches the StringCollection, it (correctly) evaluates that the property has SerializeAs == SettingsSerializeAs.Xml. However, the SerializedValue is null, and thus ToString() throws an exception.
The strange thing is that when running the debugger, the SerializedValue is null until I try viewing the value variable's properties in the locals windows. At that point, SerializedValue contains the correct XML serialized format for the StringCollection, and the program continues fine.
Why is this happening?
The issue stems probably from the fact that SerializedValue is a property that is implemented something like that:
public object SerializedValue
{
get
{
if (this._ChangedSinceLastSerialized)
return CalculateSerializedData();
else
return _cachedData;
}
}
What happens in your case, in my opinion, is that you're getting _cachedData when you first access the SettingPropertyValue, then, by viewing value via the debugger, you cause _ChangedSinceLastSerialized to be true which causes the next call to SerializedValue property to return the actual serialized value.
The next question is to find out why _ChangedSinceLastSerialized in your case is set to false. The logic of SettingsPropertyValue says (you can see it in Reflector in PropertyValue property of the class) that _ChangedSinceLastSerialized is set to true when the user either accesses the settings (in case of most object types), so for example merely accessing your setting like that: MyAppSettings.Default.MySettingObject would change _ChangedSinceLastSerialized to true.
What could happen in your case that you have code similar to this:
object storingObject = MyAppSettings.Default.MySettingObject;
// Now I will use storingObject to access the setting.
Once you do something like that and keep using storingObject instead of directly accessing MyAppSettings you can create a situation in which the object changed while _ChangedSinceLastSerialized remains false.
I hope that was helpful.
I actually dropped trying to use SettingsPropertyValue and decided to serialize the object to XML manually. I simply used the SettingsProperty.Name as a key to retrieve the values of the settings stored in Properties.Settings.Default, since it is a dictionary-like object. It fixed the issue, but of course the root cause still remains unknown. The revised code looks something like this:
// Generate parameters
Dictionary<string, string> signalparams = new Dictionary<string, string>();
// Add parameters
foreach (SettingsProperty property in Properties.Settings.Default.Properties)
{
if (property.SerializeAs == SettingsSerializeAs.String)
{
signalparams.Add(property.Name, Properties.Settings.Default[property.Name].ToString());
}
else if (property.SerializeAs == SettingsSerializeAs.Xml)
{
// Serialize collection into XML manually
XmlSerializer serializer = new XmlSerializer(Properties.Settings.Default[property.Name].GetType());
StringBuilder sb = new StringBuilder();
XmlWriter writer = XmlWriter.Create(sb);
serializer.Serialize(writer, Properties.Settings.Default[property.Name]);
writer.Close();
signalparams.Add(property.Name, sb.ToString());
}
}
In our app, we need to save properties of objects to the same database table regardless of the type of object, in the form of propertyName, propertyValue, propertyType. We decided to use XamlWriter to save all of the given object's properties. We then use XamlReader to load up the XAML that was created, and turn it back into the value for the property. This works fine for the most part, except for empty strings. The XamlWriter will save an empty string as below.
<String xmlns="clr-namespace:System;assembly=mscorlib" xml:space="preserve" />
The XamlReader sees this string and tries to create a string, but can't find an empty constructor in the String object to use, so it throws a ParserException.
The only workaround that I can think of is to not actually save the property if it is an empty string. Then, as I load up the properties, I can check for which ones did not exist, which means they would have been empty strings.
Is there some workaround for this, or is there even a better way of doing this?
We had a similar problem to this when trying to serialize strings. The only way we could resolve it was to create a StringWrapper struct or class that had the appropriate constructors. We then used this type to load and save our string values.
I also got the problem and searched the web for a solution but could not find one.
I solved it by inspecting the saved XML and fixing the empty strings, like this (feed FixSavedXaml with the output from XamlWriter):
static string FixSavedXaml(string xaml)
{
bool isFixed = false;
var xmlDocument = new System.Xml.XmlDocument();
xmlDocument.LoadXml(xaml);
FixSavedXmlElement(xmlDocument.DocumentElement, ref isFixed);
if (isFixed) // Only bothering with generating new xml if something was fixed
{
StringBuilder xmlStringBuilder = new StringBuilder();
var settings = new System.Xml.XmlWriterSettings();
settings.Indent = false;
settings.OmitXmlDeclaration = true;
using (var xmlWriter = System.Xml.XmlWriter.Create(xmlStringBuilder, settings))
{
xmlDocument.Save(xmlWriter);
}
return xmlStringBuilder.ToString();
}
return xaml;
}
static void FixSavedXmlElement(System.Xml.XmlElement xmlElement, ref bool isFixed)
{
// Empty strings are written as self-closed element by XamlWriter,
// and the XamlReader can not handle this because it can not find an empty constructor and throws an exception.
// To fix this we change it to use start and end tag instead (by setting IsEmpty to false on the XmlElement).
if (xmlElement.LocalName == "String" &&
xmlElement.NamespaceURI == "clr-namespace:System;assembly=mscorlib")
{
xmlElement.IsEmpty = false;
isFixed = true;
}
foreach (var childElement in xmlElement.ChildNodes.OfType<System.Xml.XmlElement>())
{
FixSavedXmlElement(childElement, ref isFixed);
}
}
We have written an add-on for Outlook that files emails into our CRM system. Int he process of this, it saves the Outlook Message ID as a UserField on the Message itself.
eg.
currentUserProperty = Constants.APPLICATION_NAME + "EntryID";
mailItem.UserProperties.Add(currentUserProperty,
Microsoft.Office.Interop.Outlook.OlUserPropertyType.olText,
Missing.Value,
Missing.Value).Value = entryId;
Unfortunately, this is a HUUUGGEE number, much like:
"00000000D502D779150E2F4580B1AADDF04ECDA6070097EF5A1237597748A4B4F9BFF540020800000006E9E4000068BB5B6DFC36924FAEC709A17D056583000002DE0E350000"
The problem is that when the user prints the message off, Outlook insists on including this field (beneath the From/To) and because it has no spaces, cannot wrap the ID and compresses the A4 page until it can fit on horizontally. This produces teeny-tiny email printouts.
Is there any way I can correct this? I had thought of overwriting the field OriginalEntryID (which is the one causing the problem) with one delimited by spaces but I get an exception from the COM layer. My next stop is to try and suppress the output of this and other user-defined fields on Outlook stationary.
Does anyone know how this can be achieved?
You must use .NET Reflection to fix this (as recommended by Microsoft Support). Hopefully this will be fixed in future versions of the VSTO SDK.
Suppress Outlook User Field Printing
static void SuppressUserPropertyPrinting(Outlook.MailItem message)
{
try
{ // Late Binding in .NET: https://support.microsoft.com/en-us/kb/302902
Type userPropertyType;
long dispidMember = 107;
long ulPropPrintable = 0x4; // removes PDO_PRINT_SAVEAS
string dispMemberName = String.Format("[DispID={0}]", dispidMember);
object[] dispParams;
if (message.UserProperties.Count == 0) return; // no props found (exit)
// marks all user properties as suppressed
foreach (Outlook.UserProperty userProperty in message.UserProperties.Cast<Outlook.UserProperty>())
{
if (userProperty == null) continue; // no prop found (go to next)
userPropertyType = userProperty.GetType(); // user property type
// Call IDispatch::Invoke to get the current flags
object flags = userPropertyType.InvokeMember(dispMemberName, BindingFlags.GetProperty, null, userProperty, null);
long lFlags = long.Parse(flags.ToString()); // default is 45 - PDO_IS_CUSTOM|PDO_PRINT_SAVEAS|PDO_PRINT_SAVEAS_DEF (ref: http://msdn.microsoft.com/en-us/library/ee415114.aspx)
// Remove the hidden property Printable flag
lFlags &= ~ulPropPrintable; // change to 41 - // PDO_IS_CUSTOM|PDO_PRINT_SAVEAS_DEF (ref: http://msdn.microsoft.com/en-us/library/ee415114.aspx)
// Place the new flags property into an argument array
dispParams = new object[] { lFlags };
// Call IDispatch::Invoke to set the current flags
userPropertyType.InvokeMember(dispMemberName, BindingFlags.SetProperty, null, userProperty, dispParams);
}
}
catch { } // safely ignore if property suppression doesn't work
}
I think this might be able to help me:
http://www.add-in-express.com/forum/read.php?FID=5&TID=5071#postform