I'm new to WP development and I have a problem I can't get fixed on my own.
I'm trying to set up a settings page. I copied most of it from that msdn article:
http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff769510(v=vs.105).aspx#BKMK_CreatingaSettingsPageThatDoesNotRequireaConfirmationButton
The problem is connected to this line:
<phone:PhoneApplicationPage.Resources>
<local:Page1 x:Key="appSettings" />
</phone:PhoneApplicationPage.Resources>
It behaves completly random. Most of the time VS just crashes.
I am sure that this is because VS tries to connect to the IsolatedStorage. But the Emulator just closes the App when I try to access the page.
The full code of the settings page (Page1):
Page1.xaml:
<phone:PhoneApplicationPage
x:Class="MyApp.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyApp"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
shell:SystemTray.IsVisible="True">
<phone:PhoneApplicationPage.Resources>
<local:Page1 x:Key="appSettings" />
</phone:PhoneApplicationPage.Resources>
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="MYAPP" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="settings" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<Grid x:Name="ContentGrid" Grid.Row="1">
<CheckBox Content="CheckBox Setting" Height="Auto" HorizontalAlignment="Left" Margin="60,20,0,0" Name="checkBoxSetting" VerticalAlignment="Top"
IsChecked="{Binding Source={StaticResource appSettings}, Path=CheckBoxSetting, Mode=TwoWay}" />
<ListBox Height="140" HorizontalAlignment="Left" Margin="70,150,0,0" Name="listBoxSetting"
VerticalAlignment="Top" Width="360" SelectedIndex="{Binding Source={StaticResource appSettings}, Path=ListBoxSetting, Mode=TwoWay}">
<ListBoxItem Content="Times New Roman" FontSize="24" FontFamily="Times New Roman" />
<ListBoxItem Content="Arial" FontSize="24" FontFamily="Arial" />
<ListBoxItem Content="Comic Sans MS" FontSize="24" FontFamily="Comic Sans MS" />
</ListBox>
<RadioButton Content="Choice One" Height="Auto" HorizontalAlignment="Left" Margin="60,0,0,235" Name="radioButton1" VerticalAlignment="Bottom" GroupName="GroupOne" IsChecked="{Binding Source={StaticResource appSettings}, Path=RadioButton1Setting, Mode=TwoWay}" />
<RadioButton Content="Choice Two" Height="Auto" HorizontalAlignment="Left" Margin="60,350,0,0" Name="radioButton2" VerticalAlignment="Top" GroupName="GroupOne" IsChecked="{Binding Source={StaticResource appSettings}, Path=RadioButton2Setting, Mode=TwoWay}"/>
<RadioButton Content="Choice Three" Height="Auto" HorizontalAlignment="Left" Margin="60,400,0,0" Name="radioButton3" VerticalAlignment="Top" GroupName="GroupOne" IsChecked="{Binding Source={StaticResource appSettings}, Path=RadioButton3Setting, Mode=TwoWay}"/>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
And the Page1.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.IO.IsolatedStorage;
using System.Diagnostics;
namespace Schedule
{
public partial class Page1 : PhoneApplicationPage
{
// Our settings
IsolatedStorageSettings settings;
// The key names of our settings
const string CheckBoxSettingKeyName = "CheckBoxSetting";
const string ListBoxSettingKeyName = "ListBoxSetting";
const string RadioButton1SettingKeyName = "RadioButton1Setting";
const string RadioButton2SettingKeyName = "RadioButton2Setting";
const string RadioButton3SettingKeyName = "RadioButton3Setting";
const string UsernameSettingKeyName = "UsernameSetting";
const string PasswordSettingKeyName = "PasswordSetting";
// The default value of our settings
const bool CheckBoxSettingDefault = true;
const int ListBoxSettingDefault = 0;
const bool RadioButton1SettingDefault = true;
const bool RadioButton2SettingDefault = false;
const bool RadioButton3SettingDefault = false;
const string UsernameSettingDefault = "";
const string PasswordSettingDefault = "";
/// <summary>
/// Constructor that gets the application settings.
/// </summary>
public Page1()
{
InitializeComponent();
// Get the settings for this application.
try
{
settings = IsolatedStorageSettings.ApplicationSettings;
}
catch(System.IO.IsolatedStorage.IsolatedStorageException e)
{
MessageBox.Show(e.ToString());
}
}
/// <summary>
/// Update a setting value for our application. If the setting does not
/// exist, then add the setting.
/// </summary>
/// <param name="Key"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool AddOrUpdateValue(string Key, Object value)
{
bool valueChanged = false;
// If the key exists
if (settings.Contains(Key))
{
// If the value has changed
if (settings[Key] != value)
{
// Store the new value
settings[Key] = value;
valueChanged = true;
}
}
// Otherwise create the key.
else
{
settings.Add(Key, value);
valueChanged = true;
}
return valueChanged;
}
/// <summary>
/// Get the current value of the setting, or if it is not found, set the
/// setting to the default setting.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="Key"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
public T GetValueOrDefault<T>(string Key, T defaultValue)
{
T value;
// If the key exists, retrieve the value.
if (settings.Contains(Key))
{
value = (T)settings[Key];
}
// Otherwise, use the default value.
else
{
value = defaultValue;
}
return value;
}
/// <summary>
/// Save the settings.
/// </summary>
public void Save()
{
settings.Save();
}
/// <summary>
/// Property to get and set a CheckBox Setting Key.
/// </summary>
public bool CheckBoxSetting
{
get
{
return GetValueOrDefault<bool>(CheckBoxSettingKeyName, CheckBoxSettingDefault);
}
set
{
if (AddOrUpdateValue(CheckBoxSettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a ListBox Setting Key.
/// </summary>
public int ListBoxSetting
{
get
{
return GetValueOrDefault<int>(ListBoxSettingKeyName, ListBoxSettingDefault);
}
set
{
if (AddOrUpdateValue(ListBoxSettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a RadioButton Setting Key.
/// </summary>
public bool RadioButton1Setting
{
get
{
return GetValueOrDefault<bool>(RadioButton1SettingKeyName, RadioButton1SettingDefault);
}
set
{
if (AddOrUpdateValue(RadioButton1SettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a RadioButton Setting Key.
/// </summary>
public bool RadioButton2Setting
{
get
{
return GetValueOrDefault<bool>(RadioButton2SettingKeyName, RadioButton2SettingDefault);
}
set
{
if (AddOrUpdateValue(RadioButton2SettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a RadioButton Setting Key.
/// </summary>
public bool RadioButton3Setting
{
get
{
return GetValueOrDefault<bool>(RadioButton3SettingKeyName, RadioButton3SettingDefault);
}
set
{
if (AddOrUpdateValue(RadioButton3SettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a Username Setting Key.
/// </summary>
public string UsernameSetting
{
get
{
return GetValueOrDefault<string>(UsernameSettingKeyName, UsernameSettingDefault);
}
set
{
if (AddOrUpdateValue(UsernameSettingKeyName, value))
{
Save();
}
}
}
/// <summary>
/// Property to get and set a Password Setting Key.
/// </summary>
public string PasswordSetting
{
get
{
return GetValueOrDefault<string>(PasswordSettingKeyName, PasswordSettingDefault);
}
set
{
if (AddOrUpdateValue(PasswordSettingKeyName, value))
{
Save();
}
}
}
}
}
Am I would be very happy about any help.
Thanks.
Instead of local:Page1, you're supposed to put a separate class (named AppSettings in the MSDN sample).
In your case, you have put an instance of Page1 in the XAML of... Page1! Therefore, when the runtime creates Page1, it parses the XAML, find this line of code, create the new Page1 instance, which will in turn create a Page1 instance, and so on...
Related
I wanted to embed a winform chart control in a WPF window which shall be bound to an observablecollection filled by entering data in a WPF DataGrid.
The observablecollection is needed because i fill it by using a WPF-DataGrid in which i can insert or update data.
So i added to my WPF-project the following dependencies:
- System.Windows.Forms
- System.Windows.Forms.DataVisualization
For a first test i hardcoded in the constructor of the WPF window some data in the observablecollection and bound the chart control.
In that case the display in the chart works fine.
But in the final version i want to insert and/or update data in the DataGrid and the chart shall display that data all at once.
Is it possible to manage that?
Here is the code for the window and the classes as example.
The WPF window MainWindow.xaml:
<Window x:Class="StepFunctions.MainWindow"
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:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
xmlns:winformchart="clr-namespace:System.Windows.Forms.DataVisualization.Charting;assembly=System.Windows.Forms.DataVisualization"
xmlns:local="clr-namespace:StepFunctions"
mc:Ignorable="d"
Title="StepFunctions"
Height="350"
Width="525">
<Grid x:Name="maingrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--DataGrid for insert and update of step function data-->
<DataGrid x:Name="grd_stepdata"
Grid.Row="0"
Grid.Column="0"
Margin="5"
AutoGenerateColumns="False"
CanUserAddRows="True"
RowEditEnding="grd_stepdata_RowEditEnding"
ItemsSource="{Binding StepDataSource, NotifyOnSourceUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="col_LowerComparer" Binding="{Binding LowerComparer, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Lower comparer"/>
<DataGridTextColumn x:Name="col_LowerBound" Binding="{Binding LowerBound, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Lower bound"/>
<DataGridTextColumn x:Name="col_StepValue" Binding="{Binding StepValue, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Value"/>
</DataGrid.Columns>
</DataGrid>
<!--Chart for displaying the step function data-->
<WindowsFormsHost x:Name="host"
Grid.Row="0"
Grid.Column="1"
Margin="5">
<winformchart:Chart x:Name="myWinformChart"
Dock="Fill">
<winformchart:Chart.Series>
<winformchart:Series Name="series" ChartType="Line"/>
</winformchart:Chart.Series>
<winformchart:Chart.ChartAreas>
<winformchart:ChartArea/>
</winformchart:Chart.ChartAreas>
</winformchart:Chart>
</WindowsFormsHost>
<!--Button for test-->
<Button x:Name="btn_do"
Grid.Row="2"
Grid.Column="0"
Margin="5"
Click="btn_do_Click">Do it</Button>
</Grid>
</Window>
The code-behind of MainWindow.xaml:
using StepFunctions.ViewModels;
using System.Windows;
using System.Windows.Controls;
namespace StepFunctions
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainWindowViewModel vm = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = vm;
// These lines are just for the first test.
// Normally these lines would be out-commented.
AddStepdata();
ChartDataRefresh();
}
// When the user leaves a DataGrid-row after insert or update the chart shall be refreshed.
private void grd_stepdata_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
ChartDataRefresh();
}
private void AddStepdata()
{
vm.StepDataSource.Add(new StepData("<", 10, 1));
vm.StepDataSource.Add(new StepData("<", 100, 2));
vm.StepDataSource.Add(new StepData("<", 1000, 3));
}
private void ChartDataRefresh()
{
myWinformChart.DataSource = vm.StepDataSource;
myWinformChart.Series["series"].XValueMember = "LowerBound";
myWinformChart.Series["series"].YValueMembers = "StepValue";
}
/// <summary>
/// For testing the refresh of the chart after the window was loaded.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_do_Click(object sender, RoutedEventArgs e)
{
AddStepdata();
ChartDataRefresh();
}
}
}
The viewmodel:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace StepFunctions.ViewModels
{
public class MainWindowViewModel : INotifyPropertyChanged
{
/// <summary>
/// Eventhandler for signalising that a property has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<StepData> stepdataSource = new ObservableCollection<StepData>();
public ObservableCollection<StepData> StepDataSource
{
get { return stepdataSource; }
set
{
stepdataSource = value;
RaisePropertyChanged("StepDataSource");
}
}
/// <summary>
/// Informs the target which is bound to a property, that it's source was changed and that it shall update.
/// </summary>
/// <param name="propertyName">The name of the property.</param>
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And finally the class StepData which is the base for the observablecollection:
namespace StepFunctions.ViewModels
{
/// <summary>
/// Class for data of stepfunctions.
/// </summary>
public class StepData
{
/// <summary>
/// The constructor.
/// </summary>
public StepData()
{
// Do nothing
}
public StepData(string lowerComparer, double lowerBound, double stepValue)
{
LowerComparer = lowerComparer;
LowerBound = lowerBound;
StepValue = stepValue;
}
public string LowerComparer { get; set; }
public double LowerBound { get; set; }
public double StepValue { get; set; }
}
}
I got it!
The chart has to be generated in code-behind, not in the XAML.
So the method ChartDataRefresh has to look like that:
private void ChartDataRefresh()
{
Chart myWinformChart = new Chart();
myWinformChart.Dock = System.Windows.Forms.DockStyle.Fill;
Series mySeries = new Series("series");
mySeries.ChartType = SeriesChartType.Line;
myWinformChart.Series.Add(mySeries);
ChartArea myArea = new ChartArea();
myWinformChart.ChartAreas.Add(myArea);
myWinformChart.DataSource = vm.StepDataSource;
myWinformChart.Series["series"].XValueMember = "LowerBound";
myWinformChart.Series["series"].YValueMembers = "StepValue";
host.Child = myWinformChart;
}
While entering data in the WPF-DataGrid the Winform chart control is refreshed and the data displayed as a line for checking if the given data is correct.
I'm developing a Windows 8.1 store app with C# and .NET Frameowork 4.5.1.
I'm trying to pass a PasswordBox as a parameter on a button Command. I've tested the following code on WPF and it works.
MainViewModel class:
public class MainViewModel : ViewModelBase
{
private RelayCommand<PasswordBox> doLoginCommand;
/// <summary>
/// The <see cref="UserName" /> property's name.
/// </summary>
public const string UserNamePropertyName = "UserName";
private string _userName = string.Empty;
/// <summary>
/// Sets and gets the UserName property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public string UserName
{
get
{
return _userName;
}
set
{
Set(UserNamePropertyName, ref _userName, value);
}
}
/// <summary>
///
/// </summary>
public RelayCommand<PasswordBox> DoLoginCommand
{
get { return doLoginCommand; }
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
////if (IsInDesignMode)
////{
//// // Code runs in Blend --> create design time data.
////}
////else
////{
//// // Code runs "for real"
////}
this.doLoginCommand = new RelayCommand<PasswordBox>((pb) => ExecuteDoLogin(pb), (pb) => CanDoLogin(pb));
//this.doLoginCommand = new RelayCommand<PasswordBox>((pb) => ExecuteDoLogin(pb));
}
private void ExecuteDoLogin(object parameter)
{
PasswordBox passwordBox = parameter as PasswordBox;
Debug.WriteLine(_userName);
Debug.WriteLine(passwordBox.Password);
}
private bool CanDoLogin(object parameter)
{
Debug.WriteLine("CanDoLogin");
PasswordBox passwordBox = parameter as PasswordBox;
return ((!string.IsNullOrEmpty(_userName)) &&
(!string.IsNullOrEmpty(passwordBox.Password)));
}
}
And its View:
<Page
x:Class="MyProject.W81.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyProject.W81"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding MainViewModel, Source={StaticResource Locator}}"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="33*"/>
<ColumnDefinition Width="77*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" HorizontalAlignment="Center" Height="400" Margin="0" VerticalAlignment="Center" Width="600">
<TextBox
x:Name="userName"
TextWrapping="Wrap"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="247"
Margin="10,10,10,5"/>
<PasswordBox
x:Name="userPassword"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="250"
FontFamily="Global User Interface"
Margin="10,5"/>
<Button
x:Name="loginButton"
Content="Login"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Command="{Binding DoLoginCommand}"
CommandParameter="{Binding ElementName=userPassword}"
Margin="10,5" />
</StackPanel>
</Grid>
</Page>
But on Windows 8.1 it doesn't work. The problem is that loginButton is always disabled.
I have removed CanDoLogin on doLoginCommand creation and I can get _userName and userPassword.Password values.
Any advice? Do you know why loginButton is always disabled?
Don´t you have to call RaiseCanExecuteChanged on the command then something changes? Haven´t done any WPF in a long time but in apps I always call RaiseCanExecuteChanged.
Is this case I would call RaiseCanExecuteChanged on the PasswordChanged event.
How do we take screenshot in mvvm windows phone application.
I tried the below code but it doesn't work in mvvm app as it requires uielement .Any suggests how to do this in mvvm app ?
var screenshot = new WriteableBitmap(this, null);
var screenshotname = String.Format("Screenshooter_{0}", DateTime.Now.Ticks);
using (var ms = new MemoryStream())
{
screenshot.SaveJpeg(ms, 480, 800, 0, 85);
ms.Seek(0, SeekOrigin.Begin);
var library = new MediaLibrary();
var pic = library.SavePicture(screenshotname, ms);
}
UtilityLib.ShowMessageDialog(string.Concat("Screenshot saved as ", screenshotname));
Try this:
We should register each service in ViewModelLocator, as following:
using Cimbalino.Phone.Toolkit.Services;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
/// <summary>
/// This class contains static references to all the view models in the
/// application and provides an entry point for the bindings.
/// </summary>
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (!SimpleIoc.Default.IsRegistered<IScreenshotService>())
{
SimpleIoc.Default.Register<IScreenshotService, ScreenshotService>();
}
SimpleIoc.Default.Register<MainViewModel>();
}
/// <summary>
/// Gets the main view model.
/// </summary>
/// <value>
/// The main view model.
/// </value>
public MainViewModel MainViewModel
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
var viewModelLocator = (ViewModelLocator)App.Current.Resources["Locator"];
viewModelLocator.MainViewModel.Cleanup();
}
}
Then we should implement the MainViewModel as following:
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using Cimbalino.Phone.Toolkit.Services;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight;
/// <summary>
/// This class contains properties that the main View can data bind to.
/// </summary>
public class MainViewModel : ViewModelBase
{
/// <summary>
/// The screenshot service
/// </summary>
private readonly IScreenshotService _screenshotService;
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IScreenshotService screenshotService)
{
_screenshotService = screenshotService;
TakeScreenshotCommand = new RelayCommand(TakeScreenshot);
}
/// <summary>
/// Gets the take screenshot command.
/// </summary>
/// <value>
/// The take screenshot command.
/// </value>
public ICommand TakeScreenshotCommand { get; private set; }
/// <summary>
/// Calls to.
/// </summary>
private void TakeScreenshot()
{
_screenshotService.TakeScreenshot("CimbalinoScreenshot");
}
}
for connect view model with the page we should add the ViewModelLocator instance in App.xaml:
XAML
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
and add the binding in main page like:
XAML
DataContext="{Binding MainViewModel,
Source={StaticResource Locator}}"
The MainPage.xaml can be the following:
XAML
<phone:PhoneApplicationPage x:Class="CimbalinoSample.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:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
DataContext="{Binding MainViewModel,
Source={StaticResource Locator}}"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
Orientation="Portrait"
SupportedOrientations="Portrait"
shell:SystemTray.IsVisible="True"
mc:Ignorable="d">
<!-- LayoutRoot is the root grid where all page content is placed -->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- TitlePanel contains the name of the application and page title -->
<StackPanel x:Name="TitlePanel"
Grid.Row="0"
Margin="12,17,0,28">
<TextBlock Margin="12,0"
Style="{StaticResource PhoneTextTitle2Style}"
Text="Cimbalino Sample" />
<TextBlock Margin="9,-7,0,0"
Style="{StaticResource PhoneTextTitle2Style}"
Text="ScreenshotService" />
</StackPanel>
<!-- ContentPanel - place additional content here -->
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<TextBlock TextWrapping="Wrap">This samples has the goal to show how to use Cimbalino Windows Phone Toolkit Media Library - ScreenshotService</TextBlock>
<Button Margin="0,219,0,293"
Command="{Binding TakeScreenshotCommand}"
Content="Take Screenshot" />
</Grid>
</Grid>
</phone:PhoneApplicationPage>
Source : http://code.msdn.microsoft.com/wpapps/How-to-use-Cimbalino-749562db
I implement LongListSelector because I want to group items. But this LongListSelector dont show any item when I run it.
This is my XML source:
<?xml version="1.0" encoding="utf-8"?>
<Tasks>
<Task>
<Name>first task</Name>
<DueDate>05/03/2013 00:00:00</DueDate>
<Created>03/27/2013 01:24:08</Created>
</Task>
<Task>
<Name>second task</Name>
<DueDate>05/17/2013 00:00:00</DueDate>
<Created>03/27/2013 01:24:19</Created>
</Task>
<Task>
<Name>third task</Name>
<DueDate>05/17/2013 00:00:00</DueDate>
<Created>03/27/2013 01:24:38</Created>
</Task>
</Tasks>
My XAML code:
<toolkit:LongListSelector Background="Transparent" ItemsSource="{Binding TasksByDueDate}">
<toolkit:LongListSelector.GroupHeaderTemplate>
<DataTemplate>
<Border Background="Transparent">
<Border Background="{StaticResource PhoneAccentBrush}" Width="475" Height="35" HorizontalAlignment="Left">
<TextBlock Text="{Binding Key}"
Foreground="{StaticResource PhoneForegroundBrush}"
Style="{StaticResource PhoneTextGroupHeaderStyle}"
VerticalAlignment="Bottom"/>
</Border>
</Border>
</DataTemplate>
</toolkit:LongListSelector.GroupHeaderTemplate>
<toolkit:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432" Orientation="Horizontal">
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" Width="345"/>
</StackPanel>
</DataTemplate>
</toolkit:LongListSelector.ItemTemplate>
</toolkit:LongListSelector>
MainViewModel.cs:
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Task> Tasks { get; private set; }
public IEnumerable<Group<string, Task>> TasksByDueDate { get; private set; }
public MainViewModel()
{
this.Tasks = new ObservableCollection<Task>();
this.Projects = new ObservableCollection<Project>();
this.Contexts = new ObservableCollection<Context>();
}
public bool IsDataLoaded { get; private set; }
public void LoadPlannedData()
{
try
{
Tasks.Clear();
var file = IsolatedStorageFile.GetUserStoreForApplication();
XElement xElem;
using (IsolatedStorageFileStream read = file.OpenFile("tasks.xml", FileMode.Open))
{
xElem = XElement.Load(read);
}
var tasks = from task in xElem.Elements("Task")
orderby (DateTime)task.Element("Created") descending
select task;
foreach (XElement xElemItem in tasks)
{
Tasks.Add(new Task
{
Name = xElemItem.Element("Name").Value.ToString(),
DueDate = xElemItem.Element("DueDate").Value.ToString(),
Created = xElemItem.Element("Created").Value.ToString()
});
}
TasksByDueDate = from c in Tasks
group c by c.DueDate into n
select new Group<string, Task>(n);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
this.IsDataLoaded = true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Group.cs:
public class Group<TKey, TElement> : IGrouping<TKey, TElement>
{
private readonly IGrouping<TKey, TElement> grouping;
public Group(IGrouping<TKey, TElement> unit)
{
grouping = unit;
}
public TKey Key
{
get { return grouping.Key; }
}
public IEnumerator<TElement> GetEnumerator()
{
return grouping.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return grouping.GetEnumerator();
}
}
If I show ListBox of Task then everything is Displayed well. But if I display grouped items then, no item is displayed.
Aplication runs wel (no crash) only items in LongListSelector missing.
What am I doing wrong?
I spent 2-3 days chasing down this same issue and it was a nightmare. I believe the answer is that the ItemsSource data format needs to be IList. Unfortunately that won't really help you much, but here are two classes that I've created that ... well they work at least.
My StringKeyGroup allows full text group names, whilst the AlphaKeyGroup alphabetises the list.
StringKeyGroup class
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
namespace LongListSelectorDemo.Model
{
public class StringKeyGroup<T> : ObservableCollection<T>
{
public delegate string GetKeyDelegate(T item);
public string Key { get; private set; }
public StringKeyGroup(string key)
{
Key = key;
}
public static ObservableCollection<StringKeyGroup<T>> CreateGroups(IEnumerable<T> items, CultureInfo ci, GetKeyDelegate getKey, bool sort)
{
var list = new ObservableCollection<StringKeyGroup<T>>();
foreach (var item in items)
{
var index = -1;
for (var i = 0; i < list.Count; i++)
{
if (list[i].Key.Equals(getKey(item)))
{
index = i;
break;
}
}
if (index == -1)
{
list.Add(new StringKeyGroup<T>(getKey(item)));
index = list.Count - 1;
}
if (index >= 0 && index < list.Count)
{
list[index].Add(item);
}
}
if (sort)
{
foreach (var group in list)
{
group.ToList().Sort((c0, c1) => ci.CompareInfo.Compare(getKey(c0), getKey(c1)));
}
}
return list;
}
}
}
AlphaKeyGroup class
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using Microsoft.Phone.Globalization;
namespace LongListSelectorDemo.Model
{
public class AlphaKeyGroup<T> : ObservableCollection<T>
{
/// <summary>
/// The delegate that is used to get the key information.
/// </summary>
/// <param name="item">An object of type T</param>
/// <returns>The key value to use for this object</returns>
public delegate string GetKeyDelegate(T item);
/// <summary>
/// The Key of this group.
/// </summary>
public string Key { get; private set; }
/// <summary>
/// Public constructor.
/// </summary>
/// <param name="key">The key for this group.</param>
public AlphaKeyGroup(string key)
{
Key = key;
}
/// <summary>
/// Create a list of AlphaGroup<T> with keys set by a SortedLocaleGrouping.
/// </summary>
/// <param name="slg">The </param>
/// <returns>Theitems source for a LongListSelector</returns>
private static ObservableCollection<AlphaKeyGroup<T>> CreateGroups(SortedLocaleGrouping slg)
{
return new ObservableCollection<AlphaKeyGroup<T>>(slg.GroupDisplayNames.Select(key => new AlphaKeyGroup<T>(key)).ToList());
}
/// <summary>
/// Create a list of AlphaGroup<T> with keys set by a SortedLocaleGrouping.
/// </summary>
/// <param name="items">The items to place in the groups.</param>
/// <param name="ci">The CultureInfo to group and sort by.</param>
/// <param name="getKey">A delegate to get the key from an item.</param>
/// <param name="sort">Will sort the data if true.</param>
/// <returns>An items source for a LongListSelector</returns>
public static ObservableCollection<AlphaKeyGroup<T>> CreateGroups(IEnumerable<T> items, CultureInfo ci, GetKeyDelegate getKey, bool sort)
{
var slg = new SortedLocaleGrouping(ci);
var list = CreateGroups(slg);
foreach (var item in items)
{
var index = 0;
if (slg.SupportsPhonetics)
{
//check if your database has yomi string for item
//if it does not, then do you want to generate Yomi or ask the user for this item.
//index = slg.GetGroupIndex(getKey(Yomiof(item)));
}
else
{
index = slg.GetGroupIndex(getKey(item));
}
if (index >= 0 && index < list.Count)
{
list[index].Add(item);
}
}
if (sort)
{
foreach (var group in list)
{
group.ToList().Sort((c0, c1) => ci.CompareInfo.Compare(getKey(c0), getKey(c1)));
}
}
return list;
}
}
}
Example usage, just swap the class name between StringKeyGroup and AlphaKeyGroup to see the difference:
XAML
<phone:PhoneApplicationPage
x:Class="LongListSelectorDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
<TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<phone:LongListSelector x:Name="GroupedList" IsGroupingEnabled="True" HideEmptyGroups="True">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Padding="{StaticResource PhoneTouchTargetOverhang}"
FontSize="{StaticResource PhoneFontSizeMediumLarge}" />
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
<phone:LongListSelector.GroupHeaderTemplate>
<DataTemplate>
<Border Background="{StaticResource PhoneAccentBrush}"
Padding="{StaticResource PhoneTouchTargetOverhang}">
<TextBlock Text="{Binding Key}" Style="{StaticResource PhoneTextGroupHeaderStyle}"/>
</Border>
</DataTemplate>
</phone:LongListSelector.GroupHeaderTemplate>
</phone:LongListSelector>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
XAML.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
using LongListSelectorDemo.Model;
using Microsoft.Phone.Controls;
namespace LongListSelectorDemo
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
// Sample code to localize the ApplicationBar
//BuildLocalizedApplicationBar();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (GroupedList.ItemsSource == null)
{
var foodItems = new ObservableCollection<FoodItem>();
/*---Make burger items---*/
foodItems.Add(new FoodItem("Hamburger", "Burgers"));
foodItems.Add(new FoodItem("Chicken burger", "Burgers"));
foodItems.Add(new FoodItem("Turkey burger", "Burgers"));
foodItems.Add(new FoodItem("Black bean burger", "Burgers"));
/*---Make fryer items---*/
foodItems.Add(new FoodItem("Fries", "Fryer"));
foodItems.Add(new FoodItem("Onion rings", "Fryer"));
foodItems.Add(new FoodItem("Tater tots", "Fryer"));
foodItems.Add(new FoodItem("Mozzarella sticks", "Fryer"));
/*---Make fish items---*/
foodItems.Add(new FoodItem("Salmon", "Fish"));
foodItems.Add(new FoodItem("Rainbow trout", "Fish"));
foodItems.Add(new FoodItem("Grilled tilapia", "Fish"));
GroupedList.ItemsSource = GroupedItems(foodItems);
}
}
public ObservableCollection<StringKeyGroup<FoodItem>> GroupedItems(IEnumerable<FoodItem> source)
{
return StringKeyGroup<FoodItem>.CreateGroups(source,
System.Threading.Thread.CurrentThread.CurrentUICulture, s => s.GroupName, true);
}
}
}
Here is the FoodItem class:
namespace LongListSelectorDemo.Model
{
public class FoodItem
{
public FoodItem(string name, string groupName)
{
Name = name;
GroupName = groupName;
}
public string Name { get; private set; }
public string GroupName { get; private set; }
}
}
I have a little puzzle I'm trying to solve and am not sure how to go about it...
WPF application based on MVVM approach...
I have a SubstituionDataSet class that inherits from DataSet and defines an additional collection:
namespace Lib
{
public class SubstitutionDataSet : DataSet
{
public SubstitutionDataSet()
{
TableNames = new ObservableCollection<SubstitutionDataTable>();
Tables.CollectionChanging += DataTablesCollectionChanging;
}
public ObservableCollection<SubstitutionDataTable> TableNames { get; set; }
private void DataTablesCollectionChanging(object sender, CollectionChangeEventArgs e)
{
var actionTable = (DataTable) e.Element;
if (e.Action == CollectionChangeAction.Add)
{
actionTable.Columns.CollectionChanged += DataColumnCollectionChanged;
TableNames.Add(new SubstitutionDataTable { Name = actionTable.TableName });
}
else if (e.Action == CollectionChangeAction.Remove)
{
actionTable.Columns.CollectionChanged -= DataColumnCollectionChanged;
TableNames.Remove(TableNames.First(tn => tn.Name == actionTable.TableName));
}
}
private void DataColumnCollectionChanged(object sender, CollectionChangeEventArgs e)
{
var actionColumn = (DataColumn) e.Element;
var hostTable = (DataTable) actionColumn.Table;
var hostSubsitutionTable = TableNames.First(tn => tn.Name == hostTable.TableName);
if (e.Action == CollectionChangeAction.Add)
{
hostSubsitutionTable.ColumnNames.Add(actionColumn.ColumnName);
}
else if (e.Action == CollectionChangeAction.Remove)
{
hostSubsitutionTable.ColumnNames.Remove(hostSubsitutionTable.ColumnNames.First(cn => cn == actionColumn.ColumnName));
}
}
}
}
With the SubstitutionDataTable defined as below:
namespace Lib
{
public sealed class SubstitutionDataTable: INotifyPropertyChanged
{
private string _name;
/// <summary>
/// The <see cref="Name" /> property's name.
/// </summary>
private const string NamePropertyName = "Name";
public SubstitutionDataTable()
{
ColumnNames = new ObservableCollection<string>();
}
/// <summary>
/// Gets the Name property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
{
return;
}
_name = value;
RaisePropertyChanged(NamePropertyName);
}
}
public ObservableCollection<string> ColumnNames { get; set; }
#region Implementation of INotifyPropertyChanged
/// <summary>
/// A property has changed - update bindings
/// </summary>
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
...Now this is the crux of the puzzle...
The above classes are used to define a new DataTable within a DataSet and add columns and rows and run-time. I have another Class that allows configuration of an obfuscation process, part of the configuration allows selection of a DataTable and DataColumn from the SubstituionDataSet defined above.
namespace Lib
{
public class ObfuscationParams : INotifyPropertyChanged
{
private string _dataColumn;
private string _dataTable;
private char _maskCharacter;
private int _numberCharacters;
/// <summary>
/// The <see cref="MaskCharacter" /> property's name.
/// </summary>
private const string MaskCharacterPropertyName = "MaskCharacter";
/// <summary>
/// The <see cref="DataColumn" /> property's name.
/// </summary>
private const string DataColumnPropertyName = "DataColumn";
/// <summary>
/// The <see cref="DataTable" /> property's name.
/// </summary>
private const string DataTablePropertyName = "DataTable";
# region Mask Obfuscation Properties
/// <summary>
/// Defines whether whitespace is to be trimmed or not for a Mask obfuscation.
/// </summary>
public bool IsWhiteSpaceTrimmed { get; set; }
/// <summary>
/// Defines the mask character to be used for a Mask obfuscation.
/// </summary>
public char MaskCharacter
{
get { return _maskCharacter; }
set
{
if (_maskCharacter == value)
return;
_maskCharacter = value;
RaisePropertyChanged(MaskCharacterPropertyName);
}
}
/// <summary>
/// Defines the number of masking characters to apply.
/// </summary>
public int NumberCharacters
{
get { return _numberCharacters; }
set { _numberCharacters = value < 1 ? 1 : (value > 16 ? 16 : value); }
}
/// <summary>
/// Defines the mask position for a Mask obfuscation.
/// </summary>
public MaskPosition MaskPosition { get; set; }
#endregion
# region Substitute Obfuscation Properties
/// <summary>
/// Defines which datacolumn is to be used for a Substitution obfuscation.
/// </summary>
public string DataColumn
{
get { return _dataColumn; }
set
{
if (_dataColumn == value)
return;
_dataColumn = value;
RaisePropertyChanged(DataColumnPropertyName);
}
}
/// <summary>
/// Defines which datatable is to be used for a substitition obfuscation.
/// </summary>
public string DataTable
{
get { return _dataTable; }
set
{
if (_dataTable == value)
return;
_dataTable = value;
RaisePropertyChanged(DataTablePropertyName);
_dataTable = value;
}
}
#endregion
#region Implementation of INotifyPropertyChanged
/// <summary>
/// A property has changed - update bindings
/// </summary>
[field: NonSerialized]
public virtual event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
I have the configuration working and can configure a number of obfuscations and then serialize the configuration to disk.
When I deserialize I find the bindings on the GUI don't show the correct DataTable and DataColumn selections, the DataTable just shows the fully qualified object name.
I am currently just trying to get the DataTable binding working - I know I need to rework the DataColumn binding.
The GUI (usercontrol) is defined as below:
<UserControl xmlns:igEditors="http://infragistics.com/Editors" x:Class="SubstitutionOptions"
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="421" d:DesignWidth="395">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="23" />
<RowDefinition Height="23" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="Dataset" />
<igEditors:XamComboEditor Grid.Row="0"
Grid.Column="2"
Name="tablesComboBox"
NullText="select a dataset..."
ItemsSource="{Binding DataContext.Project.SubstitutionDataSet.TableNames, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedItem="{Binding DataContext.SelectedFieldSubstitutionDataTable, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
<TextBlock Grid.Row="1"
Grid.Column="0"
Text="Column" />
<igEditors:XamComboEditor Grid.Row="1"
Grid.Column="2"
NullText="select a column..."
ItemsSource="{Binding DataContext.SelectedFieldSubstitutionDataTable.ColumnNames, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
SelectedItem="{Binding DataColumn, Mode=TwoWay}"/>
</Grid>
</UserControl>
I hope I have explained the problem sufficiently. Has anyone got any ideas on how I can either get it working using the current design or redesign the approach to achieve what I need?
OK, think I've cracked it now, not that anyone seems interested :-)
I'll post the answer for posterity though...
I changed the bindings for the Comboboxes like so...
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="Dataset" />
<igEditors:XamComboEditor Grid.Row="0"
Grid.Column="2"
NullText="select a dataset..."
ItemsSource="{Binding DataContext.VDOProject.SubstitutionDataSet.TableNames, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
Text="{Binding DataTable, Mode=TwoWay}"
SelectedItem="{Binding DataContext.SelectedFieldSubstitutionDataTable, Mode=OneWayToSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
<TextBlock Grid.Row="1"
Grid.Column="0"
Text="Column" />
<igEditors:XamComboEditor Grid.Row="1"
Grid.Column="2"
NullText="select a column..."
ItemsSource="{Binding DataContext.SelectedFieldSubstitutionDataTable.ColumnNames, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
Text="{Binding DataColumn, Mode=TwoWay}" />