PropertyRef to require ComboBox - combobox

In one of my dialogs, I have the following control:
<Control Id="EnvironmentComboBox" Type="ComboBox" Sorted="yes" ComboList="yes" Property="ENVIRONMENT" X="25" Y="110" Width="200" Height="15" />
I fill the ComboBox in elsewhere like so:
<UI>
<ComboBox Property="ENVIRONMENT">
<ListItem Text="Development" Value="Development" />
<ListItem Text="SIT" Value="SIT" />
<ListItem Text="UAT" Value="UAT" />
<ListItem Text="Production" Value="Production" />
</ComboBox>
</UI>
However, if I don't have the ComboBox bit created, the MSI will still build, and it will fail during install (2205). Thus, I would like to enforce the requirement to have a property named ENVIRONMENT. I've tried adding a PropertyRef like below to my dialog:
<PropertyRef Id="ENVIRONMENT" />
However, this doesn't seem to pick up the <ComboBox Proeprty="ENVIRONMENT">. It will pick up a regular property (<Property Id="ENVIRONMENT" Value="test" />), but that doesn't really help much.
Is there any way to require a ComboBox to be defined?
EDIT: For clarification, I intend to keep the ComboBox definition separate from the Control definition so that the dialog can be reused.

MSI can exist without Combobox items declared for the property, referenced by a element (you have the ability to type any text you need in the combobox text field in case ComboList='no').
*The element doesn't have any corresponding object in the MSI tables* (only it's child elements gets written to the MSI's ComboBox table). So I suppose this element is completely optional from WIX point.
Your error #2205 is caused by non-existance of the 'ComboBox' table at all. I suppose you have only one combobox in the installer. Theoretically it is impossible possible to detect this at compile-time as error (tables can be created in custom actions as well). The most WIX team can do is generate warning.
To avoid this error I declared a dummy combobox element in my reusable project:
<Fragment><!--This fragment is intended to fill MSI tables with dummy items so that these tables became created. Tables without items aren't created-->
<UI Id="Dummy">
<Dialog Id="DummyDlg" Width="370" Height="270" Title="Dummy" NoMinimize="yes">
<Control Id="DummyDlgComboBox" Type="ComboBox" Property="DummyComboboxProperty" Width="200" Height="17" X="100" Y="80">
<ComboBox Property="DummyComboboxProperty">
<ListItem Text="Dummy" Value="Dummy" />
</ComboBox>
</Control>
</Dialog>
</UI>
</Fragment>
and referenced this UI from my commonly-used UI sequences.
Now I don't need to worry about existance of the combobox table in any of my installers.
As for a workaround to your problem - how to enforce users not to forget to declare list items, I would do a tepmlate wix file instead of the wixlib. Something like this:
<Control Id="EnvironmentComboBox" Type="ComboBox" Sorted="yes" ComboList="yes" Property="ENVIRONMENT" X="25" Y="110" Width="200" Height="15">
<ComboBox Property="ENVIRONMENT">
<Placeholder Id="EnvironmentComboBoxItems" />
</ComboBox>
</Combobox>
and give users the only ability to reuse this code using template transformation tool you provide. It will validate if all template placeholders are provided with content.
The transformation may look like this:
<TemplateSubstitutions>
<PlaceholderContent PlaceholderId='EnvironmentComboBoxItems'>
<ListItem Text="Development" Value="Development" />
<ListItem Text="SIT" Value="SIT" />
<ListItem Text="UAT" Value="UAT" />
<ListItem Text="Production" Value="Production" />
</PlaceholderContent>
</TemplateSubstitutions>
The tool for merging them:
static void Main(string[] args)
{
var templatePath = args[0];
var templateTransformPath = args[1];
var resultPath = args[2];
var templateDoc = XDocument.Load(templatePath);
var transformationDoc = XDocument.Parse(templateTransformPath);
Dictionary<string, XElement> contents = transformationDoc.Element("TemplateSubstitutions").Elements("PlaceholderContent").ToDictionary(e => e.Attribute("PlaceholderId").Value, e => e);
var planceHolders = templateDoc.Descendants("Placeholder").ToArray();
foreach (var ph in planceHolders)
{
ph.ReplaceWith(new XElement(contents[ph.Attribute("Id").Value]).Nodes());
}
templateDoc.Save(resultPath);
}
Of course this tool isn't release yet - you may want to add some meaningful error messages to your clients and validation of provided transformations. But to avoid code complication I didn't implement that.
I use this approach in few installer projects I have currently. All templates are stored in the common location and client installers can take any file they need and transform it.
My release tool is a bit more advanced of course. It can substitute values in the attributes using wildcards - I consider it extreemely useful when reusing components. I use then template like this:
<Component Guid="{StrToGuid({ProductName}_7A51C3FD-CBE9-4EB1-8739-A8F45D46DCF5)}">
and user should provide all template properties (such as 'ProductName') in command-line of my template transformation tool. It ensures components will have unique GUIDs between different products, so they will not conflict when installed on the same machine.
NOTE: Be careful with figure brackets though - they have special meaning for WIX and MSI: http://msdn.microsoft.com/library/aa368609.aspx. But as for me it is not clear enough and I don't use them regularly. But you might need another wildcard prefix.
As for organizing the installer build process, you can add template transformation calls on pre-build of the project. But I decided to use separate build scripts instead of native Visual studio build. It looks much more simple and flexible. And I don't get nasty errors like "command 'xxx' exited with code yyy" - I always see the template transformation log, error messages etc.
Hope my reinvention of the wheel will help anybody =).

I suspect <PropertyRef> was designed the way to pick up only the "direct" definitions of properties, that is, <Property> elements. The <ComboBox> just mentions the property name in its attribute, and this is not treated as a property definition.
Add a "direct" property definition to your sample, and it should work:
<UI>
<Property Name="ENVIRONMENT" Value="" />
<ComboBox Property="ENVIRONMENT">
<ListItem Text="Development" Value="Development" />
<ListItem Text="SIT" Value="SIT" />
<ListItem Text="UAT" Value="UAT" />
<ListItem Text="Production" Value="Production" />
</ComboBox>
</UI>
And reference it with <PropertyRef> element in another place - just the way you tried.
As far as I know, such a definition won't harm the combobox part, and you'll be on the safe side with proper fragment inclusion.
Alternatively, you can reference the entire <UI> element with <UIRef> element - it should have the same effect.

Why not define your combobox inside the element? Like this:
<Control Id="EnvironmentComboBox" Type="ComboBox" Sorted="yes" ComboList="yes" Property="ENVIRONMENT" X="25" Y="110" Width="200" Height="15">
<ComboBox Property="ENVIRONMENT">
<ListItem Text="Development" Value="Development" />
<ListItem Text="SIT" Value="SIT" />
<ListItem Text="UAT" Value="UAT" />
<ListItem Text="Production" Value="Production" />
</ComboBox>
</Combobox>
In this case you won't be able to reference dialog not referencing the combobox. Maybe I understood you wrong, but it looks logically to avoid user errors rather than throwing them.
If your user needs to be able to change list items of your library-dialog, you can populate dialog items with custom action for example (though it is probably not the best approach in case of static values).
I also faced plenty of problems with reusing WIX elements and ended up with creating own template project and using some transformation of wxs files (for now quite simple, but can implement anything I need) for every installer I create. Works great and gives infinite flexibility.

Related

Accessing WPF controls from a hosting WinForm application

I have a WinForm application in which I am trying to replace my toolbar with a WPF toolbar.
My solution now contains two projects:
A WPF user control project that defines the WPF toolbar
A Windows Form Application that hosts the WPF toolbar
Enabling / disabling buttons, adding items to combos, events handling of toolbar controls will have to be defined in the WinForm application, so in order to have access to them, I did the following:
in the XAML I gave each control in the toolbar a name
In the code I defined a public get property for each control
This works fine, but I was wondering if this is the right approach. Is there a better way to do what I want?
Can you post the XAML of your toolbar? – HighCore
I've made a small example of the XAML of my toolbar as you asked.
<ToolBarTray>
<ToolBar Band="1" BandIndex="1">
<Button Name="btnDoSomething1">
<Image Source="/WpfExampToolbarCtrl;component/Images/DoSomething1.png" />
</Button>
<Button Name="btnDoSomething2">
<Image Source="/WpfExampToolbarCtrl;component/Images/DoSomething2.png" />
</Button>
<Separator />
<Menu>
<MenuItem Header="Create Item">
<MenuItem Header="Item 1" />
<MenuItem Header="Item 2" />
</MenuItem>
</Menu>
<Separator />
<ComboBox Name="comboCategory">
<ComboBoxItem Content="Category 1" IsSelected="True" />
<ComboBoxItem Content="Category 2" />
</ComboBox>
</ToolBar>
</ToolBarTray>
I thought to give each control in the toolbar a name, and in the code behind define a get property for each one. This way I could access each of the toolbar's controls from my the main form of my WinForm application, and do what I want. (Add events, disable/enable controls at runtime, add items to combos at initialization or during runtime, …).
But from what I understand from Kent Boogaart answer this is not the right approach.
I'd sooner have my WPF UI bound to a model. I would then access the model from the Winforms side and manipulate that.
To elaborate: WPF's binding infrastructure is very strong and unlike what you're used to in Winforms. WPF applications tend to follow the MVVM pattern, whereby the view's data context is a view model, and your controls in the view have bindings against properties in your view model.
Thus, what I'm suggesting is you first define a view model, then modify your WPF view to bind to properties on that view model, then access that view model from your Winforms code. Changing properties on that view model from your Winforms code will automatically update the WPF view.

WiX: ListItems not displayed in ComboBox

When I create (try to create) a ComboBox in WiX, the box receives its initial value from the corresponding property's value set earlier in the .wxs-file. This far, everythings goes as planned. When I try to change its value graphically, it displays no available list items. I have not found any necessary or relevant attributes etc in the docs that I haven't used, but also I'm quite noobish on WiX so maybe have missed something obvious. The code is below:
<Property Id="LANGUAGE" Value="Swedish" />
... cut ...
<Control Type="ComboBox" ComboList="yes" Property="LANGUAGE" Id="languages_combo" Width="..." Height="..." X="..." Y="...">
<ComboBox Property="LANGUAGE">
<ListItem Value="Swedish" />
<ListItem Value="English" />
</ComboBox>
</Control>
I want to be able to select "English" instead of "Swedish" in the drop-down, but that option is not available (and not "Swedish" for that matter - even that's the default value). Any suggestions how to solve this? I have searched the net without success, so I guess it's so basic no one has run into the same problem :-)
If it helps, here is the compilation:
candle test.wxs
light -ext WixUIExtension -sice:ICE20 test.wixobj
Attempts made by me:
Adding Text="..." to the ListItems does not help.
Replacing "ComboBox" with "ListBox" (and removeing attribute ComboList) displays the options/ListItems, but unfortunately ListBox is not the control that I want.
It's interesting when you make the same mistake over and over again, and never realize it's the good old mistake. I increased the Height attribute for Control, so the ListItems fit. Works like a charm!
I think you need to set the visible displayed text on the ListItems.
Try this:
<ComboBox Property="LANGUAGE">
<ListItem Text="English" Value="English" />
<ListItem Text="Swedish" Value="Swedish" />
</ComboBox>

Item level control over ribbon item sizes using WPF ribbon (for .NET 4) and RibbonControlSizeDefinition

According to the MSDN documentation, a ribbon:RibbonControlSizeDefinition can be used to control the size of an item on a WPF ribbon by setting the ControlSizeDefinition property. Has anyone had any success using this property? I find that it is completely ignored. I initially set it using data binding, but have also tried using the code behind file.
This question is similar, but it is correctly noted in one of the comments that the OP had used a RibbonControlGroup, and therefore was seeing the expected behaviour.
I understand that it's usually best to allow the ribbon to do it's own thing regarding sizing. Sadly that's not an option for this project.
I've listed the part of my XAML code that doesn't work below.
<ribbon:RibbonTab Header="MyTab">
<ribbon:RibbonGroup Header="MyGroup">
<ribbon:RibbonButton Label="My big button" Name="BigButton"
LargeImageSource="Images\Ribbon\assignments_duties_a2k_32.png"
SmallImageSource="Images\Ribbon\assignments_duties_a2k_16.png">
<ribbon:RibbonButton.ControlSizeDefinition>
<ribbon:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True" />
</ribbon:RibbonButton.ControlSizeDefinition>
</ribbon:RibbonButton>
<ribbon:RibbonButton Label="My little button" Name="SmallButton"
LargeImageSource="Images\Ribbon\assignments_duties_a2k_32.png"
SmallImageSource="Images\Ribbon\assignments_duties_a2k_16.png">
<ribbon:RibbonButton.ControlSizeDefinition>
<ribbon:RibbonControlSizeDefinition ImageSize="Small" IsLabelVisible="True" />
</ribbon:RibbonButton.ControlSizeDefinition>
</ribbon:RibbonButton>
</ribbon:RibbonGroup>
</ribbon:RibbonTab>
After some experimentation, I have a workaround. I tried using group-level sizing instead of item-level sizing, using the ribbon:RibbonGroup.GroupSizeDefinitions property. This works as documented. Additionally, setting this to an empty RibbonGroupSizeDefinition is enough to make the item-level properties work. My code from above becomes:
<ribbon:RibbonTab Header="MyTab">
<ribbon:RibbonGroup Header="MyGroup">
<ribbon:RibbonGroup.GroupSizeDefinitions>
<ribbon:RibbonGroupSizeDefinition>
</ribbon:RibbonGroupSizeDefinition>
</ribbon:RibbonGroup.GroupSizeDefinitions>
<ribbon:RibbonButton Label="My big button" Name="BigButton" LargeImageSource="Images\Ribbon\assignments_duties_a2k_32.png" SmallImageSource="Images\Ribbon\assignments_duties_a2k_16.png">
<ribbon:RibbonButton.ControlSizeDefinition>
<ribbon:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True" />
</ribbon:RibbonButton.ControlSizeDefinition>
</ribbon:RibbonButton>
<ribbon:RibbonButton Label="My little button" Name="SmallButton" LargeImageSource="Images\Ribbon\assignments_duties_a2k_32.png" SmallImageSource="Images\Ribbon\assignments_duties_a2k_16.png">
<ribbon:RibbonButton.ControlSizeDefinition>
<ribbon:RibbonControlSizeDefinition ImageSize="Small" IsLabelVisible="True" />
</ribbon:RibbonButton.ControlSizeDefinition>
</ribbon:RibbonButton>
</ribbon:RibbonGroup>
</ribbon:RibbonTab>

Call custom action in WIX on change of value in Combox

Am stuck with combo box and custom action in WIX installer.
I have a combo box(drop down) containing few values. I want to show some text on the screen (unique for each item in dropdown) when the user selects a value from this drop down.
In .Net we can do this easily as we have different events pre-defined. But in WIX I don't see any such event.
Has someone faced the same problem? Or can guide me on how can I get it done.
Windows Installer (the underlying technology) doesn't let you do so. Literally, it doesn't publish any event when the combobox (dropdown) value changes. You'll have to add a button, for instance, for user to click when he/she changed the value in the combobox...
Alternatively, you can switch to the EmbeddedUI technique (WiX element and MSI table), but it is much more advanced...
UPDATE: a sample of using button click to update the text.
<UI>
...
<ComboBox Property="WIX_VERSIONS">
<ListItem Value="Windows Installer XML 3.0" />
<ListItem Value="Windows Installer XML 3.5" />
<ListItem Value="Windows Installer XML 3.6" />
</ComboBox>
...
<Dialog Id="MyCustomDlg">
...
<Control Id="ComboBoxMain" Type="ComboBox" X="10" Y="60" Width="300" Height="17" Property="WIX_VERSIONS" />
<Control Id="ButtonMain" Type="PushButton" X="320" Y="60" Width="40" Height="17" Text="Show">
<Publish Property="COMBOVALUEFORMATTED" Value="You've chosen the [WIX_VERSIONS] version of the toolset" />
</Control>
<Control Id="LabelMain" Type="Text" X="10" Y="80" Width="360" Height="17" Property="COMBOVALUEFORMATTED" Text="[COMBOVALUEFORMATTED]" />
...
</Dialog>
</UI>
PushButton can publish more events, for instance, DoAction, which is used to run a custom action on button click. This might be more relevant in your case.
There is a way to do this in WiX. You just need to manufacture your own changed event.
We compare our DoAction condition to another property which will hold the previous state of the Combobox - VIRTUALWEBSITEOLD
Execute a custom action in the ComboBox when old does not equal new:
<Control Id="WebSite" Type="ComboBox" Width="180" Height="18" X="120" Y="48" ComboList="no" Property="VIRTUALWEBSITE">
<Publish Event="DoAction" Value="LansaInitVirtualFolders"><![CDATA[VIRTUALWEBSITE <> VIRTUALWEBSITEOLD]]></Publish>
</Control>
Then the Custom Action performs the same comparison as the DoAction (probably not required) and then saves the Combobox value in the OLD property.
Tstring wszWebsite = ReadProperty( _T( "VIRTUALWEBSITE") );
Tstring wszWebsiteOld = ReadProperty( _T ( "VIRTUALWEBSITEOLD" ) );
// If unchanged ignore request
if ( wszWebsite == wszWebsiteOld ) return true ;
[Do Some stuff]
// Set the saved state of the combobox so we don't get called again until it changes
if ( nResult == ERROR_SUCCESS || nResult == ERROR_NO_MORE_ITEMS)
{
WriteProperty( _T("VIRTUALWEBSITEOLD" ), wszWebsite.c_str () );
}
(Note: Also need to use Twin Dialog Pattern if updating, say, a listbox control. If your control doesn't update but Next and Back DOES update it, then Twin Dialog Pattern will ensure it updates)

Correct way to create a menu with shortcuts in WPF

What is the correct/best way to create a menu with hotkey shortcuts?
I simply want a File menu like Visual Studio's that has New, Open, Save, Save All, Exit, and a few other standard shortcuts.
It seems that InputGestureText displays the appropriate text, but since it's called "Text" and doesn't seem to trigger events, I'm going to assume that isn't the right way to do it. The Command architecture also seems fairly bulky, so I don't want to head down that path if there is a better way.
Update:
For clarity, let's say I'm using the following menu:
<Menu>
<MenuItem Header="_File">
<MenuItem Header="_New" />
<MenuItem Header="_Open" />
<Separator />
<MenuItem Header="_Save" />
<MenuItem Header="Save _All" />
<Separator />
<MenuItem Header="_Export"/>
<MenuItem Header="_Import"/>
<Separator />
<MenuItem Header="E_xit"/>
</MenuItem>
</Menu>
where Export and Import would be custom shortcuts; something that Microsoft didn't build into something like ApplicationCommands. Can you please provide a complete solution, including C# custom code if necessary? (Not only will it help me out, but I hope to help anyone else out who is searching for similar issues.)
You are correct in noting that InputGestureText does not actually set up a shortcut, it simply labels it on the menu. The correct way to do it is to use the InputBindings of the window. You can map a keyboard shortcut to any command, including your own model-defined commands. But in order for this to work, you need to use menu items bound to commands - not handle their click events directly. This is the recommended way of handling menu commands in WPF. Otherwise you'll need to resort to old fashioned keyboard event handling.
<Window.InputBindings>
<KeyBinding Key="A" Modifiers="Control" Command="{Binding MyAwesomeCommand}" />
</Window.InputBindings>
More information on MSDN.
Take a look at the ApplicationCommands class. You get the standard key gestures (accelerators) and text for free.
<MenuItem Command="ApplicationCommands.Paste" Width="75" />

Resources