I am writing a Bootstrapper Application and want to create the UI for it using the WixStandardBootstrapperApplication. I want the UI such that on the first page(Install page), I see the standard EULA and a checkbox which says I accept and a button to proceed on the next page (Options page) which should get enabled only after I select the checkbox. On the next page, I list some text and want to have another checkbox, which kind of again says I accept and a button to Install which gets enabled only after I select the checkbox.
<Page Name="Install">
<Text X="154" Y="12" Width="-65" Height="21" DisablePrefix="yes">#(loc.Title)</Text>
<Image X="120" Y="20" Width="54" Height="325" ImageFile="logo.png"/>
<Richedit Name="EulaRichedit" X="154" Y="60" Width="-21" Height="-76" TabStop="yes" FontId="0" HexStyle="0x800000" />
<Checkbox Name="OptionsCheckbox" X="-11" Y="-41" Width="246" Height="17" TabStop="yes" FontId="3" HideWhenDisabled="yes">I accept.</Checkbox>
<Button Name="WelcomeCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.InstallCloseButton)</Button>
<Button Name="OptionsButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" Text="Proceed" HideWhenDisabled="yes">Next</Button>
</Page>
<Page Name="Options">
<Text X="185" Y="11" Width="-11" Height="32" FontId="1">#(loc.OptionsHeader)</Text>
<Image X="0" Y="0" Width="177" Height="325" ImageFile="logoside.png"/>
<Text X="180" Y="61" Width="-11" Height="17" FontId="3">Some text.</Text>
<Checkbox Name="EulaAcceptCheckbox" X="180" Y="251" Width="246" Height="17" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.OptionsButton)</Checkbox>
<Button Name="InstallButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" Text="Proceed">#(loc.InstallInstallButton)</Button>
<Button Name="OptionsCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.InstallCloseButton)</Button>
</Page>
The second page(Options page) is working according to what I need - Checkbox and Install button disabled, and it gets enabled only after selecting the checkbox. But on the first page(Install page) I am not able to make it work. The button is enabled even if the checkbox is not checked. I tried different options for the Checkbox and Button Name but I am unable to make it work. What can I do to make it work? Also if you have any link for any documentation for the different options then please share. I found the help file with Thmutil schema but it doesn't list the various options for Checkboxes or Buttons.
Any suggestions welcome. Feel free to ask if anything is unclear. Thanks for your help in advance.
To do this you'll need to delve into the code for your bootstrapperapplication (WixStdBootstrapperApplication.cpp).
Luckily you're basing this off of the WixStdBootstrapperApplication which I've spend quite some time getting to know.
First thing you need to do is put the EulaAcceptCheckbox back onto the page with the actual Eula on it. When you're going to control the UI elements being enabled/disabled you need to do this from within the bootstrapper application's code. The BA owns the UI.
Now we need to change the behaviour of that EulaAcceptCheckbox so that it enables/disables the OptionsButton.
In the WndProc is where we handle all the messages generated by the user when they click on a button or scroll or do anything. Under WM_COMMAND we have a switch based on LOWORD(wParam) which is the ID of the control that raised the message.
Locate "WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX" and see that it calls pBA->OnClickAcceptCheckbox();
Here is the method
void OnClickAcceptCheckbox()
{
BOOL fAcceptedLicense = ThemeIsControlChecked(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX);
ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_INSTALL_BUTTON, fAcceptedLicense);
}
Looks super simple right? Here you just have to change WIXSTDBA_CONTROL_INSTALL_BUTTON to the WIXSTDBA_CONTROL_OPTIONS_BUTTON
We also need to set the Options button to default disabled. To do this we go into "OnChangeState" and look for the if for WIXSTDBA_PAGE_INSTALL
if (m_rgdwPageIds[WIXSTDBA_PAGE_INSTALL] == dwNewPageId) // on the "Install" page, ensure the install button is enabled/disabled correctly.
{
LONGLONG llElevated = 0;
if (m_Bundle.fPerMachine)
{
BalGetNumericVariable(WIXBUNDLE_VARIABLE_ELEVATED, &llElevated);
}
ThemeControlElevates(m_pTheme, WIXSTDBA_CONTROL_INSTALL_BUTTON, (m_Bundle.fPerMachine && !llElevated));
// If the EULA control exists, show it only if a license URL is provided as well.
if (ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_EULA_LINK))
{
BOOL fEulaLink = (m_sczLicenseUrl && *m_sczLicenseUrl);
ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_EULA_LINK, fEulaLink);
ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX, fEulaLink);
}
BOOL fAcceptedLicense = !ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX) || !ThemeControlEnabled(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX) || ThemeIsControlChecked(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX);
ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_INSTALL_BUTTON, fAcceptedLicense);
// If there is an "Options" page, the "Options" button exists, and it hasn't been suppressed, then enable the button.
BOOL fOptionsEnabled = m_rgdwPageIds[WIXSTDBA_PAGE_OPTIONS] && ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_OPTIONS_BUTTON) && !m_fSuppressOptionsUI;
ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_OPTIONS_BUTTON, fOptionsEnabled);
// Show/Hide the version label if it exists.
if (m_rgdwPageIds[WIXSTDBA_PAGE_OPTIONS] && ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_VERSION_LABEL) && !m_fShowVersion)
{
ThemeShowControl(m_pTheme, WIXSTDBA_CONTROL_VERSION_LABEL, SW_HIDE);
}
}
We need to update this block to be
if (m_rgdwPageIds[WIXSTDBA_PAGE_INSTALL] == dwNewPageId) // on the "Install" page, ensure the install button is enabled/disabled correctly.
{
// If the EULA control exists, show it only if a license URL is provided as well.
if (ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_EULA_LINK))
{
BOOL fEulaLink = (m_sczLicenseUrl && *m_sczLicenseUrl);
ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_EULA_LINK, fEulaLink);
ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX, fEulaLink);
}
BOOL fAcceptedLicense = !ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX) || !ThemeControlEnabled(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX) || ThemeIsControlChecked(m_pTheme, WIXSTDBA_CONTROL_EULA_ACCEPT_CHECKBOX);
// If there is an "Options" page, the "Options" button exists, and it hasn't been suppressed, then enable the button.
BOOL fOptionsEnabled = m_rgdwPageIds[WIXSTDBA_PAGE_OPTIONS] && ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_OPTIONS_BUTTON) && !m_fSuppressOptionsUI;
ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_OPTIONS_BUTTON, fOptionsEnabled & fAcceptedLicense);
// Show/Hide the version label if it exists.
if (m_rgdwPageIds[WIXSTDBA_PAGE_OPTIONS] && ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_VERSION_LABEL) && !m_fShowVersion)
{
ThemeShowControl(m_pTheme, WIXSTDBA_CONTROL_VERSION_LABEL, SW_HIDE);
}
}
Here we removed the elevated stuff since that goes on the install button and instead enable/disable the options button depending on whether it is defined in the theme and if the accept checkbox is checked or not.
Next you'll need to add a way to locate your new OptionsPage checkbox.
You should have an enum in your cpp file
enum WIXSTDBA_CONTROL
It should be ordered into controls on pages. Here you'll need to add a new entry for your new Options checkbox control, maybe WIXSTDBA_CONTROL_OPTIONS_CHECKBOX
Below this enum you'll have a 2-d array
static THEME_ASSIGN_CONTROL_ID vrgInitControls[] =
You'll need to add a new entry here that is inserted at the same place you inserted into your enum. The inserted array item should look like this
{ WIXSTDBA_CONTROL_OPTIONS_CEHCKBOX, L"OptionsCheckbox" }, //The string should match the Name of the checkbox in the theme xml.
Now we need a way to handle messages from this Control. Head back to WndProc and add a new case to the switch under WM_COMMAND it should be
case WIXSTDBA_CONTROL_OPTIONS_CHECKBOX:
pBA->OnClickOptionsCheckbox();
return 0;
Now add a OnClickOptionsCheckbox method to your bootstrapper application just like the OnClickAcceptCheckbox()
void OnClickOptionsCheckbox()
{
BOOL fAccepted = ThemeIsControlChecked(m_pTheme, WIXSTDBA_CONTROL_OPTIONS_CHECKBOX);
ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_INSTALL_BUTTON, fAccepted);
}
Finally, we need to add the llElevated stuff we removed from the OnChangeState WIXSTDBA_PAGE_INSTALL case to WIXSTDBA_PAGE_OPTIONS and also set the default state of the Install button
else if (m_rgdwPageIds[WIXSTDBA_PAGE_OPTIONS] == dwNewPageId)
{
HRESULT hr = BalGetStringVariable(WIXSTDBA_VARIABLE_INSTALL_FOLDER, &sczUnformattedText);
if (SUCCEEDED(hr))
{
// If the wix developer is showing a hidden variable in the UI, then obviously they don't care about keeping it safe
// so don't go down the rabbit hole of making sure that this is securely freed.
BalFormatString(sczUnformattedText, &sczText);
ThemeSetTextControl(m_pTheme, WIXSTDBA_CONTROL_FOLDER_EDITBOX, sczText);
}
}
Will get changed to
else if (m_rgdwPageIds[WIXSTDBA_PAGE_OPTIONS] == dwNewPageId)
{
LONGLONG llElevated = 0;
if (m_Bundle.fPerMachine)
{
BalGetNumericVariable(WIXBUNDLE_VARIABLE_ELEVATED, &llElevated);
}
ThemeControlElevates(m_pTheme, WIXSTDBA_CONTROL_INSTALL_BUTTON, (m_Bundle.fPerMachine && !llElevated));
BOOL fAccepted = !ThemeControlExists(m_pTheme, WIXSTDBA_CONTROL_OPTIONS_CHECKBOX) || !ThemeControlEnabled(m_pTheme, WIXSTDBA_CONTROL_OPTIONS_CHECKBOX) || ThemeIsControlChecked(m_pTheme, WIXSTDBA_CONTROL_OPTIONS_CHECKBOX);
ThemeControlEnable(m_pTheme, WIXSTDBA_CONTROL_INSTALL_BUTTON, fAccepted);
HRESULT hr = BalGetStringVariable(WIXSTDBA_VARIABLE_INSTALL_FOLDER, &sczUnformattedText);
if (SUCCEEDED(hr))
{
// If the wix developer is showing a hidden variable in the UI, then obviously they don't care about keeping it safe
// so don't go down the rabbit hole of making sure that this is securely freed.
BalFormatString(sczUnformattedText, &sczText);
ThemeSetTextControl(m_pTheme, WIXSTDBA_CONTROL_FOLDER_EDITBOX, sczText);
}
}
There are a few things I would still change around with this implementation but I would highly suggest trying to walk through what the bootstrapper application does and how it works.
If you want to change the behaviour of the UI during your install you'll need to get familiar with the code here. You can add new pages, add controls, and set variables along with some other stuff.
If this seems like a lot of work (figuring all this out myself definitely was) consider whether or not you really need this type of behaviour over the default behaviour of one of the default wixstdba themes.
Related
I'm struggling to create the UI I have in my head and to date have been fairly unsuccessful.
I'm trying to create a Main Page which hosts my NavView and inside of by NavView I wish to have a command bar which will control which NavViewItems are visible. I have created a quick image of what I'm trying to achieve.
In my example I have the home button in the command bar activated which displays
Nav Item Header
Navigation Item 1
etc...
I want to be able to click documents and have the indicator switch to documents and hide the navigation items corresponding to Home and show the navigation items corresponding to Documents.
Finally, I want the command bar to collapse when the NavView pane is compact but the user should be able to click the Command bar button and expand the command bar to change between Home, Documents etc.
Really looking for any help/advice for the best places to start.
I'm still learning the UWP controls and Xaml.
I think you should use a SplitView instead of NavigationView outside, and then,
inside the Pane of the Splitview, use a NavigationView with some trick to achieve what you desired.
Key points are:
Keep the NavigationView's PaneDisplayMode LeftComact
don't use the PaneToggleButton in the NavigationView to prevent
user from changing PaneDisplayMode by clicking it, use a custom one instead to open and close pane.
Change the PaneDisplayMode of the NavigationView to Top when
pane opens, and backt to LeftComact again when pane closes.
Here is what I have achieved with NavigationView inside Splitview.Pane:
You can decorate it and make it more visually satisfying, like adding an AutoSuggestBox or Setting button, but that's the basic. Btw, don't use the NavigationView's Setting button, as I have seen it behaving strangely here.
XAML:
<SplitView
x:Name="Split"
DisplayMode="CompactInline"
CompactPaneLength="40">
<SplitView.Pane>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Button
Click="Button_Click">
<SymbolIcon Symbol="List"/>
</Button>
<NavigationView
Grid.Row="1"
x:Name="NavView"
PaneDisplayMode="LeftCompact"
CompactPaneLength="{x:Bind Split.CompactPaneLength}"
IsBackButtonVisible="Collapsed"
IsPaneToggleButtonVisible="False"
IsSettingsVisible="False"
SelectionChanged="NavView_SelectionChanged">
<NavigationView.MenuItems>
<NavigationViewItem x:Name="HomeItem" Icon="Home" VerticalAlignment="Stretch"/>
<NavigationViewItem x:Name="DocumentItem" Icon="Document" />
<NavigationViewItem x:Name="PeopleItem" Icon="People" />
</NavigationView.MenuItems>
<ContentControl>
<ListView
x:Name="ItemList"/>
</ContentControl>
</NavigationView>
</Grid>
</SplitView.Pane>
</SplitView>
Code behind:
public sealed partial class MainPage : Page
{
public List<string> HomeItemList;
public List<string> DocumentItemList;
public List<string> PeopleItemList;
public MainPage()
{
InitializeComponent();
HomeItemList = new List<string> { "HomeItem1", "HomeItem2", "HomeItem3" };
DocumentItemList = new List<string> { "DocumentItem1", "DocumentItem2", "DocumentItem3" };
PeopleItemList = new List<string> { "PeopleItem1", "PeopleItem2", "PeopleItem3" };
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Split.IsPaneOpen = !Split.IsPaneOpen;
if (Split.IsPaneOpen)
{
NavView.PaneDisplayMode = NavigationViewPaneDisplayMode.Top;
}
else NavView.PaneDisplayMode = NavigationViewPaneDisplayMode.LeftCompact;
}
private void NavView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
if (args.SelectedItem != null)
{
Split.IsPaneOpen = true;
NavView.PaneDisplayMode = NavigationViewPaneDisplayMode.Top;
if (sender.SelectedItem == HomeItem)
ItemList.ItemsSource = HomeItemList;
else if(sender.SelectedItem == DocumentItem)
ItemList.ItemsSource = DocumentItemList;
else if(sender.SelectedItem == PeopleItem)
ItemList.ItemsSource = PeopleItemList;
}
}
}
Hope that helps.
First thing is to decide if you want to use NavigationView. In XAML controls are defined by their behavior (properties and methods that they implement), while the visual appearance is irrelevant and can be altered completely. If NavigationView is right for your task then you can alter its style partially or completely - in XAML editor right click on it, then click Edit Template > Edit a Copy. Now you'll get the XAML style definition that defines appearance of NavigationView, that's the place to start.
But it might be very well that you can't use NavigationView and that starting with SplitView might be a better idea as #Muzib said.
Not sure if this is a good idea for learning XAML, but you'll learn one thing - XAML can be customized to the great extent, but doing it may also be a very complex task.
I think there are a few problems from the UX perspective.
Not all the navigation items are shown at once and a used must expand the menu to change between the sets of items.
The positions of the navigation items changes when the navigation pane is expanded. Currently the way the control works it is though the pane is expanding to show the text of the button. With your suggested approach it would like the items jump down on open.
I wonder if would be easier to have a fixed side pane with the controls laid out like you want and no hamburger button etc. This is not so unusual, the Settings app does it.
If you do go with a fixed width pane, I recommend looking at the XAML that defines the NavigationView control, which can be found inside C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.17763.0\Generic\generic.xaml
(according to the version of your SDK). Then you can make sure to use the theme resources used by the Windows so that your custom control has a similar look and feel.
In Windows UWP, what is the difference between the ItemInvoked and SelectionChanged events for a NavigationView? The API reference states
ItemInvoked
Occurs when an item in the menu receives an interaction such as a click or tap.
SelectionChanged
Occurs when the currently selected item changes.
It seems to me that SelectionChanged can detect when navigation occurs even by some method other than clicking a NavigationView.MenuItem, so would be the better, more encompassing option to use?
Or are there different use cases for each?
The main difference would be that the SelectionChanged event is executed only once, but if you click the selected item repeatedly, it is not fired. ItemInvoked on the other hand will execute each time an item is clicked even if it is selected already.
Also - SelectionChanged event will execute when you manually set SelectedItem in code.
Another thing that might be useful to note is that ItemInvoked fires before SelectionChanged when you click on a NavigationView item
I was recently struggling with this question and having a problem when using the SelectionChanged Event. Successive clicks on the same Menu Item yielded no result.
Then I found this blog post (https://blogs.msdn.microsoft.com/appconsult/2018/05/06/using-the-navigationview-in-your-uwp-applications/) which suggested using the ItemInvoked Event for the same reason given in answer #1.
This did not work for me as posted in the Blog but after changing the code to use the InvokedItemContainer Tag property the solution worked just fine.
I have included the test code I used to verify the solution.
private void NvTopLevelNav_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
contentFrame.Navigate(typeof(SettingsPage));
}
else
{
string navTo = args.InvokedItemContainer.Tag.ToString();
if ( navTo != null)
{
switch (navTo)
{
case "Nav_Home":
contentFrame.Navigate(typeof(HomePage));
break;
case "Nav_Shop":
contentFrame.Navigate(typeof(ShopPage));
break;
case "Nav_ShopCart":
contentFrame.Navigate(typeof(CartPage));
break;
case "Nav_Message":
contentFrame.Navigate(typeof(MessagePage));
break;
case "Nav_Print":
contentFrame.Navigate(typeof(PrintPage));
break;
}
}
}
}
<Grid>
<NavigationView x:Name="nvTopLevelNav"
Loaded="NvTopLevelNav_Loaded"
Margin="0,12,0,0"
SelectionChanged="NvTopLevelNav_SelectionChanged"
ItemInvoked="NvTopLevelNav_ItemInvoked"
IsTabStop="False"
Header="Lets Go Shopping">
<NavigationView.MenuItems>
<NavigationViewItem Icon="Home" Content="Home" Tag="Nav_Home" />
<NavigationViewItem Icon="Shop" Content="Shop" Tag="Nav_Shop" />
<NavigationViewItem Content="Shopping Cart" Tag="Nav_Cart">
<NavigationViewItem.Icon>
<FontIcon Glyph="[Insert Hex Decimal Value Here. See Segoe MDL2 below for details.]"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Icon="Message" Content="Message" Tag="Nav_Message" />
<NavigationViewItem Icon="Print" Content="Print" Tag="Nav_Print" />
</NavigationView.MenuItems>
<Frame x:Name="contentFrame"></Frame>
</NavigationView>
</Grid>
I have created a Scrollviewer in WP7, which harbors 3 usercontrol, each one of which hold as their content XAML created UserControls. This works fine. This scrollviewer should be able to scroll between these items, but make this not possible for the user to scroll. So when an item in one of these contents are clicked upon, the scrollviewer slides left or right depending on the item selected, and bring into view one of the other usercontrols. I use a mediator to accomplish this:
<Grid.Resources>
<Storyboard x:Name="ItemAnimation">
<DoubleAnimation x:Name="ItemAnimationContent"
Storyboard.TargetName="Mediator"
Storyboard.TargetProperty="ScrollableWidthMultiplier"/>
</Storyboard>
</Grid.Resources>
<ScrollViewer Name="ScrollableItemPanel"
Grid.Row="2"
Grid.RowSpan="3"
Grid.ColumnSpan="3"
VerticalScrollBarVisibility="Disabled"
HorizontalScrollBarVisibility="Disabled">
<StackPanel Orientation="Horizontal">
<UserControl Name="NewsListBoxControl" Width="480" />
<UserControl Name="DetailedItemControl" Width="480"/>
<UserControl Name="ExternalBrowserItemControl" Width="480"/>
</StackPanel>
</ScrollViewer>
<local:ScrollableItemAnimationMediator x:Name="Mediator"
ScrollViewer="{Binding ElementName=ScrollableItemPanel}"/>
In basic, this works fine too, I can navigate between the items, and load upon them the content as usercontrols. But the problem lies in granting the user the abillity to scroll. Before the item scrolls, I set the hittestvisibilty to true, and the horizontalscrollbarvisibility to visible. After the animation is done, I want to grant back the hittestvisibility and set the horizontalscrollbarvisibility to Disabled again. This latter is where the problem is: when I set the horizontalscrollbarvisibility to Disabled, the scrollviewer automatically brings back into view the first of three items in the stackpanel. How can I stop this? This is the code I use to scroll the mediator:
private void CreateDetailedArticleItem( Dictionary<string, string> itemQuery )
{
_articleDetailPage.ItemQuery = itemQuery;
DetailedItemControl.Content = _articleDetailPage as UserControl;
Animate( _articleDetailPage, 0.0f, 0.5f, 250 );
}
private void Animate( IContentControl control, float from, float to, double milliseconds )
{
//this eventhandler will fire when the animation has completed
EventHandler handler = null;
//we take away the User Input just for the moment, so that we can animate without the user interfering. Also, we make horizontalScroll Visible
IsUserEnabled = false;
//we then set the content of the animation. Where from will it move, towards where and in what duration?
ItemAnimationContent.From = from;
ItemAnimationContent.To = to;
ItemAnimationContent.Duration = TimeSpan.FromMilliseconds( milliseconds );
//we start the animation
ItemAnimation.Begin( );
//we tell the new control that it will appear soon, so it can load its main content
control.ViewWillAppear( );
//also, we tell the currentcontrol that it will disappear soon, so it can unload its content and eventhandlers and so on
CurrentControl.ViewWillDisAppear( );
//the handler is a delegate. This way, it becomes rather easy and clean to fire the completed event, without creating a strong reference ( well, actually,
//we do create a strong reference, but as soon as it is fired, we remove it again, shhhh! ).
handler = delegate( object sender, EventArgs e )
{
//as stated, we remove the eventlistener again, so it won't keep firing all the time
ItemAnimation.Completed -= handler;
//after the animation, we tell the new control that it is now in screen, and can start downloading its data
control.ViewDidAppear( );
//at the same time, the "current" control has fully moved out of view, so it can now fully unload all its content.
CurrentControl.ViewDidDisAppear( );
//now, all we have to do is to make sure that the next time an item is being loaded, the new content is spoken to, not the old one
CurrentControl = control;
//and finally, enable the users input again, and remove the horizontal scrollbarvisibility
IsUserEnabled = true;
};
ItemAnimation.Completed += handler;
}
private bool IsUserEnabled
{
set
{
//when the user can control the scrollviewer, then the horizontal scrollvisibility is disabled, so that the user cannot move horizontally,
//otherwise, so we only make it visible when the program needs to animate.
ScrollableItemPanel.IsHitTestVisible = value;
ScrollableItemPanel.HorizontalScrollBarVisibility = value ? ScrollBarVisibility.Disabled : ScrollBarVisibility.Visible;
}
}
I had already asked this question, then regarded it as answered, as I thought it to be answered, namely using ScrollbarVisibility.Hidden instead of ScrollbarVisibility.Disabled, only the scrollbarvisibility stays visible this way, and the user can still scroll. Is there a native way to deal with this problem?
Any help would be greatly appreciated. Greetz
Rather than fight the behavior of the native control it may be easier to just manipulate the position of items yourself using a custom control (wrapping your other controls) which animates between different visual states (adjust the translate transform) depending on the "selected" item.
Scenario: I start out in my app's main page. I navigate to sub-page A, change a value, hit the back button and the bound TextBlock in the main page doesn't change. If I navigate to sub-page B, a TextBlock using that same binding changes. Likewise, if I go to page A again I see the changed value. If I exit the app, the new value shows up on the main page. It's just when using the back button that a refresh doesn't get triggered.
I've got all my INotifyPropertyChanged stuff working. Like I said, the binding works in every scenario besides navigating back to the main page. How do I send a message or otherwise trigger a refresh of the bindings on that page? Thanks!
Edit:
Based on the accepted answer from willmel, here's what I did:
My MainPage.xaml file has this markup:
<TextBlock Text="{Binding Title, Mode=OneWay}" />
My MainViewModel.cs file has this:
public string Title
{
get { return ProfileModel.Instance.DescriptionProfile.Title; }
}
And I added this to the MainViewModel constructor:
Messenger.Default.Register<PropertyChangedMessage<string>>(this,
(action) => DispatcherHelper.CheckBeginInvokeOnUI(
() => RaisePropertyChanged("Title")));
In another view I have the following markup:
<TextBox Grid.Row="1" Width="250" Height="100" Text="{Binding TitleEdit, Mode=TwoWay}" />
In its view model I use this when getting/setting a string:
public string TitleEdit
{
get { return ProfileModel.Instance.DescriptionProfile.Title; }
set
{
if (ProfileModel.Instance.DescriptionProfile.Title == value) return;
string oldValue = ProfileModel.Instance.DescriptionProfile.Title;
ProfileModel.Instance.DescriptionProfile.Title = value;
RaisePropertyChanged("Title", oldValue, value, true);
}
}
In your view model you want to be modified if a child page changes a property. (note here, the property is of type bool, but could be anything)
Messenger.Default.Register<PropertyChangedMessage<bool>>(this,
(action) => DispatcherHelper.CheckBeginInvokeOnUI(
() =>
{
MessageBox.Show(action.newValue.ToString());
//do what you want here (i.e. RaisePropertyChanged on a value they share)
}));
When you use RaisePropertyChanged in the child class, use the broadcasting overload.
RaisePropertyChanged("Preference", oldValue, value, true);
Finally, note that to use DispatcherHelper, you need to Add the following to your App constructor (App.xaml.cs)
DispatcherHelper.Initialize();
I've failed certification because I have an image that's all white. So when a user switches to the light theme it fails because you can't see it. How do I swap this imaged based on the theme applied?
Thank you in advance
Question Making an image control invert its colors depending on theme has a simple XAML only answer:
<Image HorizontalAlignment="Center"Stretch="None" Visibility="{StaticResource PhoneLightThemeVisibility}" Source="/MyApplication;component/imageDarkTheme.png" />
<Image HorizontalAlignment="Center" Stretch="None" Visibility="{StaticResource PhoneDarkThemeVisibility}"Source="/MyApplication;component/imageLightTheme.png" />
In XAML
<Image Source="{Binding ImageSource}" />
In the view model that contains property ImageSource
public string ImageSource
{
get
{
if( (Visibility)App.Current.Resources["PhoneDarkThemeVisibility"]
== Visibility.Visible ) {
return "/path/to/dark/image.png";
} else {
return "/path/to/light/image.png";
}
}
private set {}
}
This may not change the picture if the user tombstones your app, changes the theme and switches back to the app.
One way to handle that scenario is to cache the current theme setting in the App class constructor, then compare it with the current setting within App.Application_Activated, if they're different you'll need to somehow indicate that the above view model needs to fire a property changed notification for ImageSource.
For anyone following my comments above - I had to switch from ImageBrush to Image directly (xaml below)
<Button Tag="{Binding}" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="-10,20,0,0" BorderThickness="0" Width="105" Height="102" Click="ShowKioskOnMap_Click">
<Image Source="../images/arrow.png" Width="55" Height="53" ImageOpened="Image_ImageOpened"/>
</Button>
Next in the consructor of the page I'm using --I did the theme lookup to prevent any issues when the app state is restored from taking a phone call for instance (or just loading up the view for the first time)
InitializeComponent();
theme = ""; //field level var (could make it dark by default if needed)
if ((Visibility)App.Current.Resources["PhoneDarkThemeVisibility"] == Visibility.Visible)
{
theme = "dark";
}
else
{
theme = "light";
}
And I had to implement the following in my open event to switch based on theme
private void Image_ImageOpened(object sender, RoutedEventArgs e)
{
var brush = (sender as Image);
if (brush.Stretch == Stretch.Uniform)
{
if (theme == "light")
brush.Source = new BitmapImage(new Uri("../images/arrowLight.png", UriKind.Relative));
brush.Stretch = Stretch.Fill;
}
}