I am trying to use HyperlinkButton in my uwp app. And I am setting “NavigateUri” later in the lifecycle of the control. Default value of “ViewModel.SecondaryLink” in the below code snippet is empty. And when it is empty, it is crashing. So can we not keep the value of NavigateUri as empty for HyperlinkButton? When I initialize it in the constructor of control, it works without crash but I am getting this value from internet so I need to set it later. Please help.
<Grid
Grid.Column="1"
CornerRadius="3"
Margin="32,0,0,0">
<HyperlinkButton
Content="Learn more"
FontSize="14"
Margin="0,0,0,0"
Style="{StaticResource HyperlinkButtonStyle}"
NavigateUri="{x:Bind ViewModel.SecondaryLink, Mode=OneWay}" />
</Grid>
.cpp file
Windows::Foundation::Uri FamilyValuePropControlViewModel::SecondaryLink()
{
return Windows::Foundation::Uri(m_secondaryLink);
}
.h file
winrt::hstring m_secondaryLink{ L"" };
Crash when using HyperlinkButton with empty NavigateUri
If you use Uri type to replace string type, it will not throw exception when apply null value.
public Uri SecondaryLink { get; set; }
If you do want to use string type, please set default value when SecondaryLink is empty string.
public string SecondaryLink
{
get
{
if(_secondaryLink == null)
{
return "http://defaut";
}
else
{
return _secondaryLink;
}
}
set
{
_secondaryLink = value;
}
}
Related
As in the title I have a problem where updating a property in a viewmodel of popup doesn't update the UI. I use popups from xamarin community toolkit. I'm using a command that does this task:
async Task ShowPopup()
{
MessagingCenter.Send(AnimeGroupObservable, "AnimeGroups");
Shell.Current.ShowPopup(new MediaListGroupsPopup());
}
It sends a message with payload and shows popup. This is popup viewmodel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using OtakuApp.Models;
using Xamarin.Forms;
namespace OtakuApp.ViewModels
{
class MediaListGroupsPopupViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public ObservableCollection<Group> _AnimeGroups = new ObservableCollection<Group>();
public ObservableCollection<Group> AnimeGroups
{
get => _AnimeGroups;
set
{
if (_AnimeGroups == value)
return;
_AnimeGroups = value;
OnPropertyChanged();
}
}
public String _label;
public String label
{
get => _label;
set
{
if (value == _label)
return;
_label = value;
OnPropertyChanged();
}
}
public MediaListGroupsPopupViewModel()
{
MessagingCenter.Subscribe<ObservableCollection<Group>>(this, "AnimeGroups", (AnimeGroupObservable) =>
{
Console.WriteLine(AnimeGroupObservable[0].Name);
label = AnimeGroupObservable[1].Name;
MessagingCenter.Unsubscribe<ObservableCollection<Group>>(this, "AnimeGroups");
});
}
}
}
I'm planning on having a small collection view of labels to select from. But right now I'm struggling to update one label just for testing purposes, so you can imagine that I've tried collection view and it didn't work. Setting _label to something manually in the code shows that binding works. It's just not updating for some reason.
Popup xaml file:
<?xml version="1.0" encoding="utf-8" ?>
<xct:Popup
x:Class="OtakuApp.Popups.MediaListGroupsPopup"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
Size="300,300">
<StackLayout>
<Label Text="{Binding label}" />
</StackLayout>
</xct:Popup>
So right now I have two problems:
Label doesn't update. It's binded to a property that has INotifyPropertyChanged
Weirdly this subscription happens only the second time (and after that too, just not the first time) I open up a popup. Is this because it's in the constructor? If yes, what's the correct way to deal with it?
Also a small question - I have unsubscribe at the end of subscription. When I didn't have it and I printed out AnimeGroupObservable[0].Name, the first time it was printed one time, the second time I open up the popup two times etc. Is the unsubscribe at the end the correct way to fix this?
since you are passing a single parameter to a single page, using the constructor would be much simpler than MessagingCenter (which is great, but overkill for this scenario)
when creating the page, pass the parameter in the constructor
Shell.Current.ShowPopup(new MediaListGroupsPopup(AnimeGroupObservable));
then modify the page constructor to accept the parameter
public MediaListGroupsPopup(ObservableCollection<Group> groups)
{
// you did't show how you create your VM, but I assume it's something like this
this.BindingContext = new MediaListGroupsPopupViewModel(groups);
}
then modify your VM constructor
public MediaListGroupsPopupViewModel(ObservableCollection<Group> groups)
{
label = groups[1].Name;
}
if you really are only using a single string value, you could just pass that instead of the entire ObservableCollection
I am using WPF with databinding. I have a Combobox bound to a list of strings. I want the selected item in the list to set a field in my View Model. However, I sometimes want to override the user's selection and re-set the selected value in the Combobox but I don't seem to be able to do that.
Here's the View Model code:
public class SettingsViewModel : INotifyPropertyChanged
{
public enum RateTypes
{
[Description("128Hz")]
Hz128 = 4,
[Description("256Hz")]
Hz256 = 6,
[Description("400Hz")]
Hz400 = 7,
[Description("512Hz")]
Hz512 = 8,
[Description("600Hz")]
Hz600 = 9
}
RateTypes m_SelectedRate;
List<string> RateOptions = ((RateTypes [])Enum.GetValues(typeof(RateTypes)))
.Select(o => o.Description())
.ToList();
public string SelectedRate
{
get {return m_SelectedRate.Description();}
set
{
if (value == RateType.Hz256)
{
MessageBox.Show("256Hz not an option with your system");
m_SelectedRate= IMURate.Hz400;
}
else
{
m_SelectedRate = value;
}
OnPropertyChanged(nameof(SelectedRate));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyChanged)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyChanged);
handler(this, e);
}
}
}
and the XAML has:
<ComboBox Grid.SelectedItem="{Binding SelectedRate, Mode=TwoWay}" ItemsSource="{Binding RateOptions}">
However, when I select 256Hz in the GUI, the value displayed stays as 256Hz instead of changing to 400Hz. If I call OnPropertyChanged(SelectedRate) from a separate function, the value does change.
I've tried using SelectedValue and UpdateSourceTrigger but can't find anything that works.
Any ideas?
Unbelieveable. I spent hours searching for an answer before posting that question but then 10 minutes after posting, I thought of a new search term that led me to the answer.
I simply needed to add IsAsync="true" to SelectedValue in the XAML:
<ComboBox Grid.SelectedItem="{Binding SelectedRate, Mode=TwoWay, IsAsync="true"}" ItemsSource="{Binding RateOptions}">
Oh well, hopefully this will help someone else.
Add Delay=1 fixed the problem for me. The IsAsync=true approach worked, too, but it seems to update the combobox slower sometimes and it created a bug in my GUI where changing the Combobox value in the gui doesnt work once after App start up.
<ComboBox Grid.SelectedItem="{Binding SelectedRate, Mode=TwoWay, Delay=1}" ItemsSource="{Binding RateOptions}">
I'm developing windows phone 8 application . And using the Looping Selector functionality for bind list of city name.
I get city name list in json format.
Now i need to bind the result to looping selector as data source
XAML CODE
<DataTemplate x:Key="NumberTemplate">
<Grid>
<TextBlock
Text="{Binding }"
FontSize="54"
FontFamily="{StaticResource PhoneFontFamilySemiBold}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</DataTemplate>
<toolkit:LoopingSelector
x:Name="loopselector"
Grid.Column="1"
Margin="12"
Width="128"
ItemSize="128,128"
ItemTemplate="{StaticResource NumberTemplate}">
<toolkit:LoopingSelector.DataSource>
<local:NumbersDataSource Minimum="0" Maximum="50" />
</toolkit:LoopingSelector.DataSource>
</toolkit:LoopingSelector>
CS CODE
public void Citybind()
{
try
{
string city_nameurl = "http://xxxxxxx.yyyyyy";
WebClient city_namewc = new WebClient();
city_namewc.DownloadStringAsync(new Uri(city_nameurl), UriKind.Relative);
city_namewc.DownloadStringCompleted += city_namewc_DownloadStringCompleted;
}
catch (Exception ex)
{
}
}
void city_namewc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
try
{
var city_name = e.Result;
loopselector.ItemTemplate = city_name; /* Error Cannot convert string to system.windows.dataTemplat*/
}
You can use a popular Json converter library NewtonSoft.Json for this purpose. Just add it to your project from Nuget package manager in visual studio. Then in your city_namewc_DownloadStringCompleted handler, write something like this :
var city_name = e.Result;
ObservableCollection cityList = JsonConvert.DeserializeObject>(city_name);
loopselector.ItemTemplate = city_name;
loopselector.ItemSource = cityList;
This code snippet assumes that your json is just a plain list of strings. If it is different, then you need to adjust the type <> you provide to JsonConvert for deserialization. Hope this helps!
I am working on a Silverlight for Windows Embedded project. I defined a custom user control which consists of a TextBlock control and an image control. I want to specify different text in different instance of the custom control. So I did the following in Expression Blend:
In Blend code-behind (C#) UserControl1.xaml.cs I defined and registered a DependencyProperty and set DataContext of the TextBlock:
namespace WindowsEmbeddedSilverlightApplication1
{
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
ItemText.DataContext = this;
}
public String MyText
{
get { return (String)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(String), typeof(UserControl1), null);
}
}
In UserControl1.xaml:
<UserControl
......
x:Class="WindowsEmbeddedSilverlightApplication1.UserControl1"
d:DesignWidth="196" d:DesignHeight="85">
<Grid x:Name="LayoutRoot">
<Image x:Name="ItemImage" Margin="0,0,90,0"/>
<TextBlock x:Name="ItemText" HorizontalAlignment="Right" Width="68" Text="{Binding MyText}" TextWrapping="Wrap" Height="23" VerticalAlignment="Bottom"/>
</Grid>
</UserControl>
To use the custom user control in MainPage.xaml:
<UserControl
......
xmlns:local="clr-namespace:WindowsEmbeddedSilverlightApplication1"
x:Class="WindowsEmbeddedSilverlightApplication1.MainPage"
Width="640" Height="480">
<Grid x:Name="LayoutRoot" Background="White">
<local:UserControl1 HorizontalAlignment="Left" Margin="94,117,0,0" Width="196" Height="85" VerticalAlignment="Top" MyText="Text1"/>
<local:UserControl1 HorizontalAlignment="Left" Margin="94,217,0,0" Width="196" Height="85" VerticalAlignment="Top" MyText="Text2"/>
</Grid>
</UserControl>
So with these when I run the application in Expression Blend I am able to see the correct text displayed on the two instances of the custom user control.
Then I import the project into Visual Studio Silverlight for Windows Embedded Application. I read some posts mentioning that I have to redo the registeration. So I did the following:
In UserControl1.h:
static HRESULT Register()
{
HRESULT hr = E_FAIL;
hr = XRCustomUserControlImpl<UserControl1>::Register (__uuidof(UserControl1), L"UserControl1", L"clr-namespace:WindowsEmbeddedSilverlightApplication1");
hr = RegisterDependencyProperties();
return hr;
}
static HRESULT RegisterDependencyProperties();
HRESULT SetMyText(WCHAR* pText);
HRESULT GetMyText(BSTR* pbstrText);
public:
static DEPENDENCY_PROPERTY_ID m_dpMyTextID;
In UserControl1.cpp:
HRESULT UserControl1::RegisterDependencyProperties()
{
HRESULT hr = E_FAIL;
IXRApplication* pApplication = NULL;
XRDependencyPropertyMetaData dpm = XRDependencyPropertyMetaData();
dpm.Size = sizeof(XRDependencyPropertyMetaData);
dpm.pfnPropertyChangeNotification = NULL;
dpm.pfnTypeConverter = NULL;
dpm.pfnEnumerableCreation = NULL;
XRValue defValue;
defValue.vType = VTYPE_READONLY_STRING;
defValue.pReadOnlyStringVal = L"Default";
dpm.DefaultValue = defValue;
hr = GetXRApplicationInstance(&pApplication);
hr = pApplication->RegisterDependencyProperty(L"MyText", VTYPE_BSTR, ControlID(), &dpm, &m_dpMyTextID);
pApplication->Release();
return hr;
}
HRESULT UserControl1::SetMyText( WCHAR* pText )
{
HRESULT hr = E_FAIL;
XRValue xrValue;
xrValue.vType = VTYPE_READONLY_STRING;
xrValue.pReadOnlyStringVal = pText;
hr = SetPropertyValue(m_dpMyTextID, &xrValue);
return hr;
}
HRESULT UserControl1::GetMyText( BSTR* pbstrText )
{
HRESULT hr = E_FAIL;
XRValue xrValue;
hr = GetPropertyValue(m_dpMyTextID, &xrValue);
if (SUCCEEDED(hr))
{
*pbstrText = xrValue.bstrStringVal;
}
return hr;
}
I didn't change anything in the generated MainPage.h and MainPage.cpp code.
Compilation is successful, and execution is also ok. The custom controls are displayed, but the texts that I put in the xaml are not displayed.
Am I doing something wrong or missing something? I couldn't find much information on this topic on the Internet. Could anyone point me in the right direction. Thanks!
Problem solved.
I added a callback to XRDependencyPropertyMetaData.pfnPropertyChangeNotification:
dpm.pfnPropertyChangeNotification = NamePropertyChanged;
According to MSDN, PFN_PROPERTY_CHANGE is a callback method that XAML for Windows Embedded invokes when the value of the associated property changes. Indeed this callback is called during initialization. In the callback, I set the TextBlock text to the new value:
void UserControl1::NamePropertyChanged(__in IXRDependencyObject* pControl, __in XRValue* pOldValue, __in XRValue* pNewValue)
{
HRESULT hr;
if (pControl && pOldValue && pNewValue)
{
UserControl1 *tempObj;
pControl->QueryInterface(__uuidof(UserControl1), (void**)&tempObj);
hr = tempObj->m_pItemText->SetText(pNewValue->pReadOnlyStringVal);
}
}
So the way of binding DependencyProperty value to a control in C++ code-behind is different from C# code-behind.
I defined my complete viewmodel using XAML:
<local:TestViewModel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:icColors"
SampleProperty="Sample Text Property Value">
<local:TestViewModel.Questions>
....
</local:TestViewModel.Questions>
</local:TestViewModel>
How can parse this XAML at runtime and set as a property of my application, App.TestViewModel?
You can parse XAML at runtime using the XAMLReader class. Simply parse your XAML using the XamlReader.Load method, then assign it (remembering to cast the result). Here is some example code:
System.Windows.Resources.StreamResourceInfo streamInfo = System.Windows.Application.GetResourceStream(uri);
if ((streamInfo != null) && (streamInfo.Stream != null))
{
using (System.IO.StreamReader reader = new System.IO.StreamReader(streamInfo.Stream))
{
TestViewModel vm = System.Windows.Markup.XamlReader.Load(reader.ReadToEnd()) as TestViewModel;
}
}