Changing page through the NavigationView control causes some data bindings to fail/be lost.
data model:
public class Flashpoint
{
private string name;
private string description;
private string image;
public string Name
{
get { return name; }
set { name = value; }
}
public string Description
{
get { return description; }
set { description = value; }
}
public string Image
{
get { return image; }
set { image = value; }
}
}
page xaml:
<Page
x:Class="Braytech_3.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="using:Braytech_3.Views"
xmlns:data="using:Braytech_3.Models"
Style="{StaticResource PageStyle}"
mc:Ignorable="d">
<Grid x:Name="ContentArea">
<StackPanel Height="400" HorizontalAlignment="Left" VerticalAlignment="Top">
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Source="{x:Bind CurrentFlashpoint.Image, Mode=OneWay}" Stretch="UniformToFill"/>
<TextBlock Text="{x:Bind CurrentFlashpoint.Name, Mode=OneWay}" />
</StackPanel>
page cs:
namespace Braytech_3.Views
{
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
Flashpoint CurrentFlashpoint { get; set; }
public MainPage()
{
InitializeComponent();
Render();
}
public event PropertyChangedEventHandler PropertyChanged;
private void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private async void Render()
{
StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder;
StorageFile APIData = await tempFolder.GetFileAsync("APIData.json");
string json = await FileIO.ReadTextAsync(APIData);
Universal request = JsonConvert.DeserializeObject<Universal>(json);
foreach (var challenge in request.response.data.challenges)
{
if (challenge.type == "flashpoint")
{
CurrentFlashpoint = new Models.Flashpoint
{
Name = challenge.name,
Description = challenge.description,
Image = $"/Assets/Images/{ challenge.slug }.jpg"
};
}
}
}
}
}
Omitted from the above code excerpts is an ObservableCollection that is bound to the pivot control below the image control. It works as expected. Vendors page also uses an ObservableCollection. As I am only trying to bind single items (textblock, image), I wasn't sure how I could use an ObservableCollection and if I even should attempt to as to me it's the equivalent of an array of items where as I'm trying to bind a single item.
Using the live tree inspector I was able to inspect the image control, before and after the data binding is lost, but it didn't lead me to an answer as to why.
Why does it lose its binding and how can I fix it?
The better way is that you could set NavigationCacheMode="Required" for MainPage to avoid repeat loading.
The page is cached and the cached instance is reused for every visit regardless of the cache size for the frame.
This is official document that you could refer.
Related
I'm new to C# and UWP. After many days of reading official microsoft docks and stackoverflow I still cannot understand what am I doing wrong.
Here is extremely simple test app.
I am trying to change grid's background on click. When application starts the grid's background is bg-1.jpg as expected. After click the value of property PicturePath have changed, but Grid's background have not.
What am I missing?
Here is a simple xaml page
<Page
x:Class="Test.Views.StartPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid PointerPressed="Grid_PointerPressed">
<Grid.Background>
<ImageBrush ImageSource="{Binding PicturePath, Mode=OneWay}" />
</Grid.Background>
</Grid>
</Page>
Code behind
public sealed partial class StartPage : Page
{
public StartPage()
{
this.InitializeComponent();
DataContext = new {
TestBackgroundClass.getInstance().PicturePath,
};
}
private void Grid_PointerPressed(object sender, PointerRoutedEventArgs e)
{
TestBackgroundClass.getInstance().PicturePath = "ms-appx:///Assets/bg-2.jpg";
}
}
and a singleton class
public class TestBackgroundClass : INotifyPropertyChanged
{
private static TestBackgroundClass instance;
private string _picturePath { get; set; }
public string PicturePath
{
get
{
return _picturePath;
}
set
{
_picturePath = value;
NotifyPropertyChanged();
}
}
public TestBackgroundClass()
{
_picturePath = "ms-appx:///Assets/bg-1.jpg"
}
public static TestBackgroundClass getInstance()
{
if (instance == null) instance = new TestBackgroundClass();
return instance;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I am very new to this MVVM idea, was trying to write my first MVVM application to get my feet wet. Got a little bit of progress but hit road block as I progress. My question is a very basic fundamental one that could be easily addressed with code behind, but it was discouraged to do so as it violates the loose coupling principal I guess. So this is the problem I have:
My Model:
public class ToDoItemModel
{
private DateTime _TodoDate;
private string _TodoDescription;
private TimeSpan _TodoTimeSpan;
private string _StartTime;
public string StartTime
{
get { return _StartTime; }
set
{
_StartTime = value;
}
}
public TimeSpan ToDoTimeSpan
{
get { return _TodoTimeSpan; }
set
{
_TodoTimeSpan = value;
}
}
public string ToDoDescription
{
get { return _TodoDescription; }
set
{
_TodoDescription = value;
}
}
public DateTime ToDoDate
{
get { return _TodoDate; }
set
{
_TodoDate = value;
}
}
public override string ToString()
{
return string.Format("Date: {0}- Time: {1}- Duration: {2}- Description: {3}",_TodoDate.ToString("d"),_StartTime,_TodoTimeSpan,_TodoDescription);
}
}
My viewModel:
public class ToDoListModelView:INotifyPropertyChanged
{
List<ToDoItemModel> _myModel = new List<ToDoItemModel>();
public ICommand AddToDo
{
get
{
return new RelayCommand(addToDo);
}
}
public ToDoListModelView()
{
_myModel.Add(new ToDoItemModel() { ToDoDate = DateTime.Now, ToDoDescription = "Testing 1" });
_myModel.Add(new ToDoItemModel() { ToDoDate = DateTime.Now.AddDays(1), ToDoDescription = "Testing 2" });
}
public List<ToDoItemModel> myModel
{
get { return _myModel; }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler!=null)
{
handler(this, e);
}
}
public void RaisePropertyChanged(string PropertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(PropertyName));
}
private void addToDo()
{
_myModel.Add(new ToDoItemModel() { ToDoDate = DateTime.Now.AddDays(2), ToDoDescription = "From Relay Command" });
RaisePropertyChanged("DataGridChanged");
}
}
The viewModel implements INotifyPropertyChanged, and use a class RelayCommand that implements ICommand interface.
My view is:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWPF" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="TestWPF.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid Margin="1,0,-1,0" d:DataContext="{d:DesignData /SampleData/ToDoListModelSampleData.xaml}">
<Button x:Name="AddToDoButton" Content="Add Todo Item
"
HorizontalAlignment="Left" Margin="419,90,0,0" VerticalAlignment="Top" Width="78" Height="33.04"
Command="{Binding AddToDo}"/>
<DataGrid x:Name="TodoList" HorizontalAlignment="Left"
Margin="33,184,0,0" VerticalAlignment="Top"
RenderTransformOrigin="-0.833,-0.846" Height="108" Width="464"
ItemsSource="{Binding myModel}"
Style="{DynamicResource ToDoEntry}"/>
</Grid>
</Window>
When I hit F5 the application ran and the 2 initial records were displayed.
However, when I hit the button add, I saw the new record was added internally but the datagrid was not updated. I know the datagrid needs to be refreshed somehow, but I am not sure how to tell the grid to refresh the way MVVM is supposed to handle.
My current code behind is as simple as that:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ToDoListModelView();
}
}
According to the documentation I read from Microsoft site and if I understand it correctly, RaisePropertyChange is supposed to flag a change event occurs, I am just confused how to tie the event to refresh the grid using xaml without any code behind. Please help.
Change your List<ToDoItemModel> to an ObservableCollection<ToDoItemModel>
A List<T> does not notify the UI to update when an item gets added or removed from the collection, while an ObservableCollection<T> does.
You need to Use Binding Mode TwoWay also change your List to ObservableCollection
An ObservableCollection will notify the UI when a record is added or removed.
ObservableCollection<ToDoItemModel> _myModel = new ObservableCollection<ToDoItemModel>();
public ICommand AddToDo
{
get
{
return new RelayCommand(addToDo);
}
}
public ToDoListModelView()
{
_myModel.Add(new ToDoItemModel() { ToDoDate = DateTime.Now, ToDoDescription = "Testing 1" });
_myModel.Add(new ToDoItemModel() { ToDoDate = DateTime.Now.AddDays(1), ToDoDescription = "Testing 2" });
}
public ObservableCollection <ToDoItemModel> myModel
{
get { return _myModel; }
}
XAML:
<DataGrid x:Name="TodoList" HorizontalAlignment="Left"
Margin="33,184,0,0" VerticalAlignment="Top"
RenderTransformOrigin="-0.833,-0.846" Height="108" Width="464"
ItemsSource="{Binding myModel,Mode=TwoWay}"
Style="{DynamicResource ToDoEntry}"/>
Use ObservableCollection instead of Lists (as it was told above), and set
UpdateSourceTrigger=PropertyChanged in the XAML.
CODE:
<DataGrid x:Name="TodoList" HorizontalAlignment="Left"
Margin="33,184,0,0" VerticalAlignment="Top"
RenderTransformOrigin="-0.833,-0.846" Height="108" Width="464"
ItemsSource="{Binding Path=myModel,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged }"
Style="{DynamicResource ToDoEntry}"/>
I bind a class which derived from INotifyPropertyChange to a Datacontext.
after some interaction, a value will be calculated and output property will be updated.
My problem is that the result textbox didn't update at all.
public partial class setraSubWpfTolerance : UserControl
{
public setraFit objSource = new setraFit();
public setraSubWpfTolerance()
{
InitializeComponent();
this.DataContext = objSource;
}
}
And the class:
public class setraFit : INotifyPropertyChanged
{
private readonly CollectionView _BoreSystems;
public CollectionView BoreSystems
{
get { return _BoreSystems; }
}
private decimal? _MaxBoreDimension;
public decimal? MaxBoreDimension
{
get { return _MaxBoreDimension; }
set
{
if (_MaxBoreDimension == value) return;
_MaxBoreDimension = value;
onPropertyChanged("MaxBoreDimension");
}
}
private string _BoreSystem;
public string BoreSystem
{
get { return _BoreSystem; }
set
{
if (_BoreSystem == value) return;
_BoreSystem = value;
calcBoreDimension();
onPropertyChanged("BoreSystem");
}
}
public setraFit()
{
IList<string> listBore = setraStaticTolerance.getBoreList();
_BoreSystems = new CollectionView(listBore);
}
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void calcBoreDimension()
{
_MaxBoreDimension = (decimal)100.035;
}
}
Last but not least the XAML
<UserControl x:Class="SetraSubForms.setraSubWpfTolerance"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="375">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="194,10,0,0" Name="BoreSystemComboBox" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Path=BoreSystems}"
SelectedValue="{Binding Path=BoreSystem}"/>
<TextBox HorizontalAlignment="Left" Margin="194,67,0,37" Name="MaxDimBoreTextBox" Width="120" IsReadOnly="False"
Text="{Binding Path=MaxBoreDimension, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</Grid>
</UserControl>
I expected to receive the dummy value of 100.035 after changing the combobox but the textbox did not update. If i run step by step i can see the "MaxBoreDimension" property of setraFit is changed.
What did i do wrong?
Thanks in advance for your help
sittingDuck
Your method is updating the private value, not the Property:
private void calcBoreDimension()
{
_MaxBoreDimension = (decimal)100.035;
}
Change to
private void calcBoreDimension()
{
MaxBoreDimension = (decimal)100.035;
}
You're doing the same thing in the constructor, which is causing your calcBoreDimension method to not run:
public setraFit()
{
IList<string> listBore = setraStaticTolerance.getBoreList();
_BoreSystems = new CollectionView(listBore);
}
should be
public setraFit()
{
IList<string> listBore = setraStaticTolerance.getBoreList();
BoreSystems = new CollectionView(listBore); //this line!
}
When you create properties that point to private fields, you should almost never have to set the private field anywhere other than the property. This is why properties exist- so that whenever you get or set them, you will run the code in the get and set blocks instead of just retrieving the current value.
SOLVED!
The key is to initate the PropertyChanged event for the "MaxBoreDimension"
public decimal? NominalDimension
{
get { return _NominalDimension; }
set
{
if (_NominalDimension == value) return;
_NominalDimension = value;
calcBoreDimension();
onPropertyChanged("NominalDimension");
onPropertyChanged("MaxBoreDimension");
}
}
Thanks DLeh for the contribution.
I'm a little noobish so sorry if this is really simple...
I have ~120 pictures and I would like to be able to select and display one of those pictures when the name in the combobox corresponds to that picture. I would like to know how to populate the combobox with the name of each picture and select a picture from the box without making 120 "if" statements? Each image has it's own filename in the project.Properties.Resources folder
Also, if there's a way to rename 120 pictures really quickly, that would save me a lot of time but it's fine if i just have to do it by hand.
The form is WPF and I'm using Visual studio 2012 Express
You can create a Model to hold your image data and populate a ObservableCollection<T> of that model, Then we can bind that collection to the ComboBox and set the ComboBox selectedItem to a Image control
Here is an example:
Code:
namespace WpfApplication14
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<MyImage> _images = new ObservableCollection<MyImage>();
private MyImage _selectedImage;
public MainWindow()
{
InitializeComponent();
// Add image files to collection
foreach (var image in Directory.GetFiles(#"C:\your image directory"))
{
// set name and path in model
Images.Add(new MyImage { Path = image, Name = System.IO.Path.GetFileNameWithoutExtension(image) });
}
}
public ObservableCollection<MyImage> Images
{
get { return _images; }
set { _images = value; }
}
public MyImage SelectedImage
{
get { return _selectedImage; }
set { _selectedImage = value; NotifyPropertyChanged("SelectedImage"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class MyImage
{
public string Name { get; set; }
public string Path { get; set; }
}
}
Xaml:
<Window x:Class="WpfApplication14.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name="UI">
<StackPanel DataContext="{Binding ElementName=UI}">
<TextBlock Text="Images" />
<ComboBox ItemsSource="{Binding Images}" SelectedItem="{Binding SelectedImage}" DisplayMemberPath="Name"/>
<TextBlock Text="Selected Image" />
<Image Source="{Binding SelectedImage.Path}" />
</StackPanel>
</Window>
Result:
Well WPF memories are really rusty but as a generalization, you need to know where the selected image in in List. What you can do is keep track of index/positoin of each image in list in a dictionary:
private imageIndices=new Dictionary<string, int>();
store indices:
imageIndices[<IMAGE_NAME>] = <INDEX_OF_IMAGE>;
get name of image from dropdown and get where that image is from dictionary:
var n=<IMAGE_NAME>;
list.Select(imageIndices[<IMAGE_NAME>]);//use actual method name to select
Hey so I was wondering what UIElement does skype use for its Contact List in order to display additional details within each ListItem (such as Status Updates, Avatars, Online Status and so forth)?
(source: iforce.co.nz)
As far as I know the regular System.Windows.Forms.ListBox only lets you display a single line of text to represent that Object.
(source: iforce.co.nz)
I'm looking to recreate something similar but for a different purpose, but I haven't been able to find much on google (So that's why I'm seeing if anyone here has experience modifying the design of a ListBox, to gain a better perspective over each detail).
Thanks
Here is a very basic example of creating a custom Datatemplate for WPF.
First you create your Model with all the properties you want to be displayed, then bind a list of them to your ListBox. Then you can create a DataTemplate this is basically a xaml(visual) representation of your Model, you can make it look however you want and you can use any of the properties from your Model in the DataTemplate.
Example:
Window code:
public partial class MainWindow : Window
{
private ObservableCollection<MyListBoxItemModel> _listBoxItems = new ObservableCollection<MyListBoxItemModel>();
public MainWindow()
{
InitializeComponent();
ListBoxItems.Add(new MyListBoxItemModel { Title = "Item 1", Image = new BitmapImage(new Uri("http://icons.iconarchive.com/icons/custom-icon-design/mini/32/Search-icon.png")) });
ListBoxItems.Add(new MyListBoxItemModel { Title = "Item 2", Image = new BitmapImage(new Uri("http://icons.iconarchive.com/icons/custom-icon-design/mini/32/Search-icon.png")) });
ListBoxItems.Add(new MyListBoxItemModel { Title = "Item 3", Image = new BitmapImage(new Uri("http://icons.iconarchive.com/icons/custom-icon-design/mini/32/Search-icon.png")) });
}
public ObservableCollection<MyListBoxItemModel> ListBoxItems
{
get { return _listBoxItems; }
set { _listBoxItems = value; }
}
}
Model:
public class MyListBoxItemModel : INotifyPropertyChanged
{
private string _title;
private string _line2 = "Line2";
private BitmapImage _image;
public string Title
{
get { return _title; }
set { _title = value; NotifyPropertyChanged("Title"); }
}
public string Line2
{
get { return _line2; }
set { _line2 = value; NotifyPropertyChanged("Line2"); }
}
public BitmapImage Image
{
get { return _image; }
set { _image = value; NotifyPropertyChanged("Image"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
Xaml:
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication7"
Title="MainWindow" Height="351" Width="464" Name="UI" >
<Window.Resources>
<!-- The tempate for MyListBoxItemModel -->
<DataTemplate DataType="{x:Type local:MyListBoxItemModel}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}" />
<StackPanel>
<TextBlock Text="{Binding Title}" FontWeight="Medium" />
<TextBlock Text="{Binding Line2}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding ElementName=UI, Path=ListBoxItems}" />
</Grid>
</Window>
This is just a simple example with an Image and some text, but it should get you started, Just modify the DataTemplate and Model how you want to display the snp code data
Result: