I have a ListBox which contains a StackPanel of two items, an Image and a Textblock. At the user's request, I would like to be able to toggle the visibility of the TextBlock on or off, thus only showing the Images. As it is now, the Image and TextBlock combination for each item is stacked vertically, and the Image is a perfect square (which ultimately creates a rectangular shape when the TextBlock is shown under each image). When the user wishes to hide the TextBlock, I would like to have the ListBox show only the StackPanel items as uniform squares for the Images (hopefully that made sense).
What I have is as follows
<ListBox Name="ListBoxEffects" SelectionMode="Single" ItemsSource="{Binding}" Margin="{Binding}"
toolkit:TiltEffect.IsTiltEnabled="True" SelectionChanged="ListBox_SelectionChanged"
ItemContainerStyle="{StaticResource ListBoxItemStyle1}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel ItemWidth="159" ItemHeight="Auto" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" >
<Image Source="{Binding Thumbnail}" Width="155" Height="155" />
<TextBlock Text="{Binding Name}" Visibility="{Binding TextBlockVisibility}" TextWrapping="Wrap" FontSize="{StaticResource PhoneFontSizeNormal}" VerticalAlignment="Center" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And in the ApplicationBar created in the code behind is where I have a menu item which will allow a user to select his or her preference on showing or hiding the TextBlock under each Image
private void BuildLocalizedApplicationBar()
{
ApplicationBar = new ApplicationBar();
ApplicationBarMenuItem showFilterNamesMenuItem = new ApplicationBarMenuItem();
if (Settings.ShowFilterNames.Value)
showFilterNamesMenuItem.Text = "Hide names";
else
showFilterNamesMenuItem.Text = "Show names";
showFilterNamesMenuItem.Click += showFilterNamesMenuItem_Click;
ApplicationBar.MenuItems.Add(showFilterNamesMenuItem);
}
void showFilterNamesMenuItem_Click(object sender, EventArgs e)
{
if(Settings.ShowFilterNames.Value)
{
((ApplicationBarMenuItem)ApplicationBar.MenuItems[0]).Text = "Hide names";
Settings.ShowFilterNames.Value = false;
//Toggle the text block visibility to show text here
}
else
{
((ApplicationBarMenuItem)ApplicationBar.MenuItems[0]).Text = "Show names";
Settings.ShowFilterNames.Value = true;
//Toggle the text block visibility to hide text here
}
}
And a check is performed when the page is navigated to so that the TextBlock's under each image can be shown or hidden appropriately
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (Settings.ShowFilterNames.Value)
//Show the TextBlocks here
else
//Hide the TextBlocks here
}
As far as I can tell the above implementation does toggle the menu item text correctly and saves the user's preference so that upon returning the menu item text is displayed according to the last selection the user chose, but I am unsure of how to change the visibility of the TextBlock underneath each image in the ListBox?
EDIT**
BooleanToVisibilityConverter.cs
//Error on BooleanToVisibilityConverter stating does not implement interface member 'System.Windows.Data.IValueConverter.Convert(object, System.Type, object, System.Globalization.CultureInfo)
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo language)
{
return (value is bool && (bool)value) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo language)
{
return value is Visibility && (Visibility)value == Visibility.Visible;
}
}
and in XAML
xmlns:common="clr-namespace:TestApp.Common"
<phone:PhoneApplicationPage.Resources>
<common:BooleanToVisibilityConverter x:Key="BoolToVisConv" />
</phone:PhoneApplicationPage.Resources>
<ListBox Name="ListBoxEffects" SelectionMode="Single" ItemsSource="{Binding}" Margin="{Binding}"
toolkit:TiltEffect.IsTiltEnabled="True" SelectionChanged="ListBox_SelectionChanged"
ItemContainerStyle="{StaticResource ListBoxItemStyle1}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel ItemWidth="159" ItemHeight="Auto" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" >
<Image Source="{Binding Thumbnail}" Width="155" Height="155" />
<TextBlock Text="{Binding Name}" Visibility="{Binding IsTextBlockVisible, Converter={StaticResource BoolToVisConv}}" TextWrapping="Wrap" FontSize="{StaticResource PhoneFontSizeNormal}" VerticalAlignment="Center" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Use this method to find out your Textblock for each ListBoxItem
public static T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
{
try
{
int childCount = VisualTreeHelper.GetChildrenCount(parentElement);
if (childCount == 0)
return null;
for (int i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(parentElement, i);
if (child != null && child is T)
{
return (T)child;
}
else
{
var result = FindFirstElementInVisualTree<T>(child);
if (result != null)
return result;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return null;
}
This Method will return First element of specified type from your DataTemplate. And allows you to work with that indivisual element.
For this You can use following code snippet
for(i=0;i<ListBoxEffects.count;i++)
{
ListBoxItem item = ListBoxEffects.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;
StackPanel TargetStackPanel = common.FindFirstElementInVisualTree<StackPanel>(item);
TextBlock TargetTextBlock= TargetStackPanel.Children[1] as TextBlock;
TargetTextBlock.Visibility = Visibility.Visible;
ListBoxEffects.UpdateLayout();
}
Use the above code to show or hide the textblocks respectively Just by changing the line
TargetTextBlock.Visibility = Visibility.Visible;
or
TargetTextBlock.Visibility = Visibility.Collapsed;
The Visibility property is an enum of type Visibility. This would be quite a bit easier if it were a boolean, but it's not.
You should define a static resource to instantiate a BooleanToVisibility converter, then bind the Visibility property to a boolean property in your DataContext. Here's a working example:
<Window x:Class="WpfApplication4.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">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConv" />
</Window.Resources>
<StackPanel>
<TextBlock Text="Hide me" Visibility="{Binding IsTextBlockVisible, Converter={StaticResource BoolToVisConv}}" />
<Button Content="Toggle TextBlock" Name="ToggleItButton" Click="ToggleItButton_Click" />
</StackPanel>
public partial class MainWindow : Window, INotifyPropertyChanged {
private bool m_IsTextBlockVisible = true;
public bool IsTextBlockVisible {
get { return m_IsTextBlockVisible; }
set { m_IsTextBlockVisible = value; NotifyPropertyChanged("IsTextBlockVisible"); }
}
public MainWindow() {
InitializeComponent();
DataContext = this;
}
private void ToggleItButton_Click(object sender, RoutedEventArgs e) {
IsTextBlockVisible = !IsTextBlockVisible;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name) {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
You can create two DataTemplate. One DataTemplate with name and the other one without name.
XAML:
<Window ...
>
<Window.Resources>
<DataTemplate x:Key="TemplateWithName">
<StackPanel Orientation="Vertical" >
<Image Source="{Binding Thumbnail}" Width="155" Height="155" />
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="TemplateWithoutName">
<StackPanel Orientation="Vertical" >
<Image Source="{Binding Thumbnail}" Width="155" Height="155" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel>
<ToggleButton x:Name="tbName" Content="Name" Click="tbName_Click" />
</StackPanel>
<ListBox Name="ListBoxEffects" SelectionMode="Single" ItemsSource="{Binding}"
Grid.Row="1" ItemTemplate="{StaticResource TemplateWithName}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel ItemWidth="159" ItemHeight="Auto" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
</Window>
Code-behind:
private void tbName_Click(object sender, RoutedEventArgs e)
{
if (tbName.IsChecked.Value)
{
ListBoxEffects.ItemTemplate = this.FindResource("TemplateWithoutName") as DataTemplate;
}
else
{
ListBoxEffects.ItemTemplate = this.FindResource("TemplateWithName") as DataTemplate;
}
}
Matthew, addition to your question:
public class BooleanToVisibilityConverter : IValueConverter
{
private object GetVisibility(object value)
{
if (!(value is bool))
return Visibility.Collapsed;
bool objValue = (bool)value;
if (objValue)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object Convert(object value, Type targetType, object parameter, string language)
{
return GetVisibility(value);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Related
This is the structure of the XAML:
<controls:MasterDetailsView
ItemsSource="{x:Bind Artists}">
<controls:MasterDetailsView.MasterHeader>
// Some Code
</controls:MasterDetailsView.MasterHeader>
<controls:MasterDetailsView.ItemTemplate>
// Some Code
</controls:MasterDetailsView.ItemTemplate>
<controls:MasterDetailsView.DetailsTemplate>
<DataTemplate x:DataType="data:ArtistView">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<RelativePanel HorizontalAlignment="Stretch">
// Some Code
</RelativePanel>
<ListView
x:Name="AlbumsListView"
Grid.Row="1"
HorizontalAlignment="Stretch"
ItemsSource="{x:Bind Albums}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:AlbumView">
<Grid Margin="10,0,0,30">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<RelativePanel>
// Some Code
</RelativePanel>
<ListView
x:Name="SongsListView"
Grid.Row="1"
HorizontalAlignment="Stretch"
ContainerContentChanging="SongsListView_ContainerContentChanging"
IsItemClickEnabled="True"
ItemClick="SongsListView_ItemClick"
ItemsSource="{x:Bind Songs}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Music">
// Some Code
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</controls:MasterDetailsView.DetailsTemplate>
</controls:MasterDetailsView>
I am trying to highlight an item of SongsListView (it's inside another listview) whose music is playing by changing the foreground. I implemented it in the ContainerContentChanging but the foreground will only be changed after reload the page. I want it to be updated in real time. How can I do that?
I registered a MusicSwitching event which will take place when the current playing music is changed, so that I can set the foreground of the item that has been played to black, and the foreground of the item to be played to a highlight color.
public async void MusicSwitching(Music current, Music next)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
{
var item = ArtistMasterDetailsView.Items.First((a) => (a as ArtistView).Name == next.Artist);
var container = ArtistMasterDetailsView.ContainerFromItem(item);
});
}
I can first find the item correctly, but the container is null. Why? I thought it is the DataTemplate that contains the item.
If you want to highlight an item of SongsListView,the simple way is to set a property(I saw you have set a IsPlaying method,I changed it to a property) in Music class to bind to the control in item you want to hightlight.About Binding,you can refer to this document.
First, you should let the Music class inherits from INotifyPropertyChanged to listen property change notifications.
public class Music : IComparable<Music>, INotifyPropertyChanged
{
......
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public bool IsPlaying {
set {
IsMusicPlaying = value;
OnPropertyChanged();
}
get {
return IsMusicPlaying;
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Then,bind the property to the control in item you want to highlight.
<Page.Resources>
<local:ColorConvert x:Name="MyConvert"></local:ColorConvert>
</Page.Resources>
<FontIcon
x:Name="PlayingIcon"
Margin="0,0,16,0"
FontFamily="Segoe MDL2 Assets"
Foreground="{StaticResource SystemColorHighlightColor}"
Glyph=""
RelativePanel.AlignLeftWithPanel="True"
Visibility="{Binding Name, Converter={StaticResource MusicVisibilityConverter}}" />
<TextBlock
x:Name="MusicNameTextBlcok"
Grid.Column="1"
Width="720"
Text="{x:Bind Name}"
Foreground="{x:Bind IsPlaying,Converter={StaticResource MyConvert},Mode=OneWay}"/>
.cs
public class ColorConvert : IValueConverter
{
// this does not include sublevel menus right now
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value.ToString() == "False")
{
return Helper.BlackBrush;
}
else
{
return Helper.GetHighlightBrush();
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Finally,when the MusicSwitching is triggered,you can reset IsPlaying property.In this case,the foreground will also change together.
I'm writing a program that dynamically creates Control based on the data type of the properties extracted using reflection. Here is the view in subject for examination.
<ListView ItemsSource="{Binding PropertyControls}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"></TextBlock>
<UserControl FontSize="14" Content="{Binding Path=PropertyValue, Converter={StaticResource PropertyValueConverter}}"></UserControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I created an item template for the items in ListView. Each row consists of two elements; the label and the dynamically created control.
For instance, if the PropertyValue is a boolean, then the dynamically created control will be a checkbox. If the PropertyValue is a string, then the dynamically created control will be a TextBox. If the PropertyValue is a list of FileInfo, then a separate window will be created with another ListView and browse button with OpenFileDialog.
I was able to accomplish the dynamically created control by creating a class that implements IValueConverter and which is utilized as specified in the XAML. The PropertyValueConverter converts the PropertyValue into a dynamically created control by inspecting its data type.
My problem is when the CheckBox is checked, there was no event raised and the ViewModel is not modified by its changes. I suspect because the binding in the XAML was made to the UserControl and not to its child control which happens to be a CheckBox. Although it is possible to bind the IsChecked programmatically in the PropertyValueConverter, is there a better way to solve this?
------- Revision 1 -------
public class PropertyControl: INotifyPropertyChanged
{
public string PropertyName { get; set; }
private object propertyValue;
public object PropertyValue
{
get { return propertyValue; }
set
{
propertyValue = value;
OnPropertyChanged(nameof(PropertyValue));
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
/// <summary>
/// Dynamically converts between value and control given a data type - control mapping.
/// </summary>
class PropertyValueConverter: IValueConverter
{
/// <summary>
/// Converts from value to control.
/// </summary>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof (int))
return new NumberTextBox {Text = value.ToString()};
if (targetType == typeof (string))
return new TextBox {Text = value.ToString()};
if (targetType == typeof (bool))
return new CheckBox {IsChecked = (bool) value};
throw new Exception("Unknown targetType: " + targetType);
}
/// <summary>
/// Converts from control to value.
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof (NumberTextBox))
return (value as NumberTextBox).Value;
if (targetType == typeof(TextBox))
return (value as TextBox).Text;
if (targetType == typeof(CheckBox))
return (value as CheckBox).IsChecked;
throw new Exception("Unknown targetType: " + targetType);
}
}
------- Revision 2 -------
public partial class SettingsWindow : Window
{
public BindingList<SettingViewModel> ViewModels { get; set; }
private SettingsManager settingsManager = new SettingsManager(new SettingsRepository());
public SettingsWindow()
{
InitializeComponent();
// Reloads the data stored in all setting instances from database if there's any.
settingsManager.Reload();
// Initialize setting view model.
ViewModels = SettingViewModel.GetAll(settingsManager);
}
private void ResetButton_OnClick(object sender, RoutedEventArgs e)
{
settingsManager.Reload();
}
private void SaveButton_OnClick(object sender, RoutedEventArgs e)
{
settingsManager.SaveChanges();
}
}
--- Tab Control ---
<TabControl Name="ClassTabControl" TabStripPlacement="Left" ItemsSource="{Binding ViewModels}">
<TabControl.Resources>
<utilities:PropertyValueConverter x:Key="PropertyValueConverter" />
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"
Margin="8" FontSize="14"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding PropertyControls}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"></TextBlock>
<CheckBox FontSize="14" IsChecked="{Binding Path=PropertyValue, Converter={StaticResource PropertyValueConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></CheckBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" Grid.Row="1" Margin="8" HorizontalAlignment="Center">
<Button Name="ResetButton" Padding="4" Content="Reset" FontSize="14" Margin="4"
Click="ResetButton_OnClick"></Button>
<Button Name="SaveButton" Padding="4" Content="Save" FontSize="14" Margin="4"
Click="SaveButton_OnClick"></Button>
</StackPanel>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
A much easier way is to create templates based on the type of your property. First of all you have to add the system namespace to access all the basic types:
xmlns:System="clr-namespace:System;assembly=mscorlib"
Now you can get rid of your converter and do it all in XAML like:
<DataTemplate>
<StackPanel x:Name="itemStackPanel" Orientation="Horizontal" Margin="8">
<!-- General part -->
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"/>
<!-- Specific (property based) part -->
<ContentPresenter Content="{Binding PropertyValue}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type System:String}">
<TextBlock Text="{Binding ElementName=itemStackPanel, Path=DataContext.PropertyValue}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:Boolean}">
<CheckBox IsChecked="{Binding ElementName=itemStackPanel, Path=DataContext.PropertyValue}"/>
</DataTemplate>
<!-- ... -->
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
</DataTemplate>
You simply create a template for every possible type like you need it. The ContentPresenter selects the right template based on the type of PropertyValue. Since you are going to bind to a parent from out of your template you have to use a element to bind on PropertyValue (described in Access parent DataContext from DataTemplate).
/edit Ok, some one was faster :/
Here's the example (without INotifyPropertyChanged because I did not want to write too much code ;))
public interface IViewModel
{
string PropertyName { get; set; }
}
public class StringViewModel : IViewModel
{
public string PropertyName { get; set; }
public string Content { get; set; }
}
public class BooleanViewModel : IViewModel
{
public string PropertyName { get; set; }
public bool IsChecked { get; set; }
}
public class MainViewModel
{
public ObservableCollection<IViewModel> ViewModels { get; set; }
public MainViewModel()
{
ViewModels = new ObservableCollection<IViewModel>
{
new BooleanViewModel {PropertyName = "Bool", IsChecked = true },
new StringViewModel {PropertyName = "String", Content = "My text"}
};
}
}
<Window x:Class="WpfApplication2.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:local="clr-namespace:WpfApplication2"
mc:Ignorable="d"
xmlns:viewModel="clr-namespace:WpfApplication2"
Title="MainWindow">
<Grid>
<ListView ItemsSource="{Binding ViewModels}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Margin="8">
<TextBlock Text="{Binding PropertyName}" />
<ContentControl FontSize="14" Content="{Binding .}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModel:StringViewModel}">
<TextBox Text="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:BooleanViewModel}">
<CheckBox IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
So i've been trying to build a simple rss feed app for win10 using universal apps and mvvmcross that will do the following:
Load all items from an address in a listbox or something similar which will show them in the splitview.
When clicking on a title it will open a WebView with the full article of the specific title.
The problem I have, is binding different parts of the same RssItems to different controls but keep their relation. I'm new to both technologies but i think it should be possible somehow, I just can't find the way.
These are the related parts of the code I wrote:
viewModel.cs:
class FirstViewModel : MvxViewModel
{
private List<string> _rssItems;
public List<string> RssItems
{
get { return _rssItems; }
set { _rssItems = value; RaisePropertyChanged(() => RssItems); }
}
public MvxCommand SelectionChangedCommand
{
get
{
return new MvxCommand(() =>
{
LoadRssItems();
});
}
}
private async void LoadRssItems()
{
List<string> feedItems = new List<string>();
SyndicationClient rssReaderClient = new SyndicationClient();
SyndicationFeed rssFeed = await rssReaderClient.RetrieveFeedAsync(new Uri("xml address"));
if (rssFeed != null)
{
foreach (var item in rssFeed.Items)
{
feedItems.Add(item.Title.Text);
RssItemsOrinigal.Add(item);
}
RssItems = feedItems;
}
}
And the firstView.xaml:
<views:MvxWindowsPage
x:Class="RssReader.Views.FirstView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:RssReader.Views"
xmlns:views="using:MvvmCross.WindowsUWP.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<RelativePanel>
<Button x:Name="HamburgerButton"
RelativePanel.AlignLeftWithPanel="True"
FontFamily="Segoe MDL2 Assets"
FontSize="36"
Content=""
Click="HamburgerButton_Click"/>
<SplitView Grid.Row="1"
x:Name="sv"
DisplayMode="CompactOverlay"
OpenPaneLength="200"
CompactPaneLength="50">
<SplitView.Pane>
<ListBox x:Name="lstMenuItems" SelectionMode="Single">
<ListBoxItem x:Name="ListBoxItem1">
<StackPanel Orientation="Horizontal">
<Button FontFamily="Segoe MDL2 Assets" FontSize="36" Command="{Binding SelectionChangedCommand}"></Button>
<TextBlock FontSize="24" Text="Website 1" />
</StackPanel>
</ListBoxItem>
</ListBox>
</SplitView.Pane>
<SplitView.Content>
<ListBox ItemsSource="{Binding RssItems}"/>
</SplitView.Content>
</SplitView>
</Grid>
</views:MvxWindowsPage>
One way to do this is using ContentPresenter together with your ListBox and set the Visibility property each time when you select an item or want to go back to the ListBox:
<Page.Resources>
<DataTemplate x:Key="DetailContent">
<WebView Source="{Binding DetailUri}" />
</DataTemplate>
<DataTemplate x:Key="ListBoxContent">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</Page.Resources>
...
<SplitView.Content>
<Grid>
<ContentPresenter Content="{x:Bind listBox.SelectedItem, Mode=OneWay}"
ContentTemplate="{StaticResource DetailContent}" Visibility="{x:Bind VM.IsVisible, Mode=OneWay}" />
<ListBox x:Name="listBox" ItemsSource="{x:Bind VM.RssItems}"
ItemTemplate="{StaticResource ListBoxContent}" SelectionChanged="{x:Bind VM.listBox_SelectionChanged}" />
<Button Content="Close and Show ListBox" VerticalAlignment="Bottom" Visibility="{x:Bind VM.IsVisible, Mode=OneWay}"
Click="{x:Bind VM.IsVisible_Clicked}" />
</Grid>
</SplitView.Content>
In the ViewModel of this page for example:
public class Page6ViewModel : INotifyPropertyChanged
{
public ObservableCollection<ListBoxDetail> RssItems;
private Visibility _IsVisible = Visibility.Collapsed;
public Visibility IsVisible
{
get { return _IsVisible; }
set
{
if (value != _IsVisible)
{
_IsVisible = value;
OnpropertyChanged();
}
}
}
public Page6ViewModel()
{
RssItems = new ObservableCollection<ListBoxDetail>();
RssItems.Add(new ListBoxDetail { Title = "Item 1", DetailUri = "http://stackoverflow.com/questions/38853708/using-different-parts-of-rss-feed-in-a-universal-app-using-mvvmcross-and-command?noredirect=1#comment65137456_38853708" });
RssItems.Add(new ListBoxDetail { Title = "Item 2", DetailUri = "https://msdn.microsoft.com/en-us/library/windows/apps/ms668604(v=vs.105).aspx" });
RssItems.Add(new ListBoxDetail { Title = "Item 3", DetailUri = "https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.itemscontrol.itemssource.aspx" });
}
private ListBox listBox;
public void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
listBox = sender as ListBox;
listBox.Visibility = Visibility.Collapsed;
IsVisible = Visibility.Visible;
}
public void IsVisible_Clicked(object sender, RoutedEventArgs e)
{
listBox.Visibility = Visibility.Visible;
IsVisible = Visibility.Collapsed;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnpropertyChanged([CallerMemberName]string propertyName = "")
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Another common method is using Frame as Content of SpiltView, then create a page for ListBox and a page for WebView, at first navigate this frame to the page of ListBox, in the SelectionChanged event, you can send the uri as parameter and navigate this frame to the page of WebView. For this method, there are many samples on the internet and you can search for them.
Wanting to get the color combobox (see image) behavior in my WPF ListView column.
Can someone help me get this started? I am comfortable with ListView binding but not sure how to implement this.
EDIT:
xmlns:System="clr-namespace:System;assembly=mscorlib"
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type System:Enum}"
x:Key="ColorList">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="Windows.Media.Color" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
Tells me type provided must be an enum.
Best Answer I have found:
How can I list colors in WPF with XAML?
ComboBox with ItemTemplate
You will have to use ItemTemplate for you ComboBox items:
<ComboBox ItemsSource="{Binding NamedColors}"
xmlns:converters="clr-namespace:TestLab.WPF">
<ComboBox.Resources>
<converters:ColorToSolidBrushConverter x:Key="ColorToBrush"/>
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border BorderThickness="0" Height="20" Width="20"
Background="{Binding Value, Converter={StaticResource ColorToBrush}}"/>
<TextBlock Text="{Binding Key}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Brush converter
Also, you will need a color to brush converter, because binding doesn't do it automatically:
public class ColorToSolidBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new SolidColorBrush((Color)value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Color name - color pair creation
And here is how you can create the color Name - color pairs (currently it is an instance method in the main Window class, but you can refactor it to some helper class):
private IEnumerable<KeyValuePair<String, Color>> GetColors()
{
return typeof(Colors)
.GetProperties()
.Where(prop =>
typeof(Color).IsAssignableFrom(prop.PropertyType))
.Select(prop =>
new KeyValuePair<String, Color>(prop.Name, (Color)prop.GetValue(null)));
}
Window code
And this is the window:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.NamedColors = this.GetColors();
this.DataContext = this;
}
public IEnumerable<KeyValuePair<String, Color>> NamedColors
{
get;
private set;
}
}
ObjectDataProvider
Some code file:
public namespace SomeNamespace
{
public static class ColorHelper
{
public static IEnumerable<KeyValuePair<String, Color>> GetColors()
{
return typeof(Colors)
.GetProperties()
.Where(prop =>
typeof(Color).IsAssignableFrom(prop.PropertyType))
.Select(prop =>
new KeyValuePair<String, Color>(prop.Name, (Color)prop.GetValue(null)));
}
}
}
XAML object data provider:
...
xmlns:someNamespace="clr-namespace:SomeNamespace"
...
<ObjectDataProvider MethodName="GetColors"
ObjectType="{x:Type someNamespace.ColorHelper}"
x:Key="ColorList">
</ObjectDataProvider>
XAML comboBox:
<ComboBox ItemsSource="{Binding ColorList}" ...
Should be something like this :
<ComboBox ItemsSource="{Binding ItemSourceObs}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Fill="{Binding Color}" Height="10" Width="10" />
<TextBlock Text="{Binding DisplayedText}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Where DisplayesText and Color (type of Brushes) are properties of an object lets say A and ItemSourceObs is ObservableCollection of type A
This approach is based on MVVM pattern
Using code behind working solution :
<ComboBox x:Name="ComboColor" Width="50" Height="50" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Fill="{Binding Name}" Width="16" Height="16" Margin="0,2,5,2" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And code behind :
public MainWindow()
{
InitializeComponent();
ComboColor.ItemsSource = typeof(Colors).GetProperties();
}
Working Example :
I have a table called groups as shown below :
After looking at the image above I think you might have understood that primary key and foreign key exist in the same table. I think this is what developers call cyclic reference.
In MainWindow.xaml I have a DataGrid which contains three columns namely Group Name, Parent Name, Description. The xaml looks like :
<Window .......>
<Window.DataContext>
<self:MainWindowViewModel />
</Window.DataContext>
<DataGrid ItemsSource="{Binding Groups}" TabIndex="1">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Group Name" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding GroupName}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding GroupName}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Parent" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ParentID, Converter={StaticResource GroupIDToGroupNameConverter}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.GroupsCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
SelectedValue="{Binding ParentID}"
SelectedValuePath="GroupID"
DisplayMemberPath="GroupName"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Description" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Description}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</power:PowerDataGrid.Columns>
</power:PowerDataGrid>
</Window>
Now I have a ViewModel called MainWindowViewModel
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
SampleDBContext sampleDBContext = new SampleDBContext();
Groups = new ObservableCollection<Group>();
GroupsCollection = new ObservableCollection<Group>(from g in sampleDBContext.Groups select g);
}
private ObservableCollection<Group> _groups;
public ObservableCollection<Group> Groups
{
get
{
return _groups;
}
set
{
_groups = value;
OnPropertyChanged("Groups");
}
}
private ObservableCollection<Group> _groupsCollection;
public ObservableCollection<Group> GroupsCollection
{
get
{
return _groupsCollection;
}
set
{
_groupsCollection = value;
OnPropertyChanged("GroupsCollection");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertryName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertryName));
}
}
#endregion
}
GroupIDToGroupName.cs //Converter
public class GroupIDToGroupName : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
SampleDBContext sampleDBContext = new SampleDBContext();
return (from g in sampleDBContext.Groups
where g.GroupID == (int)value
select g.GroupName).FirstOrDefault();
}
else
{
return "";
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SampleDBContext sampleDBContext = new SampleDBContext();
return (from g in sampleDBContext.Groups
where g.GroupName == (string)value
select g.GroupID).FirstOrDefault();
}
}
In App.xaml :
<self:GroupIDToGroupName x:Key="GroupIDToGroupNameConveerter" />
My Case (Very similar to above sample):
I just want to use a Multi-Column ComboBox instead of simple ComboBox inside DataGrid.
I have two tables :
Now I have set up my code exactly as the above mentioned code.
I have added an extra class called GroupIDAndNameWithCorrespondingEffect like :
public class GroupIDAndNameWithCorrespondingEffect : INotifyPropertyChanged
{
private int _groupID;
public int GroupID
{
get
{
return _groupID;
}
set
{
_groupID = value;
OnPropertyChanged("GroupID");
}
}
private string _groupName;
public string GroupName
{
get
{
return _groupName;
}
set
{
_groupName = value;
OnPropertyChanged("GroupName");
}
}
private string _correspondingEffect;
public string CorrespondingEffect
{
get
{
return _correspondingEffect;
}
set
{
_correspondingEffect = value;
OnPropertyChanged("CorrespondingEffect");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Changes in my ViewModel :
I removed the property GroupsCollection and all its references and added a new property called GroupIDAndNamesWithCorrespondingEffects as below :
private ObservableCollection<GroupIDAndNameWithCorrespondingEffect> _groupIDAndNamesWithCorrespondingEffects;
public ObservableCollection<GroupIDAndNameWithCorrespondingEffect> GroupIDAndNamesWithCorrespondingEffects
{
get
{
return _groupIDAndNamesWithCorrespondingEffects;
}
set
{
_groupIDAndNamesWithCorrespondingEffects = value;
OnPropertyChanged("GroupIDAndNamesWithCorrespondingEffects");
}
}
And in the Constructor :
List<GroupIDAndNameWithCorrespondingEffect> _GroupIDAndNamesWithCorrespondingEffects = (
from g in sampleDBContext.Groups
select new GroupIDAndNameWithCorrespondingEffect
{
GroupID = g.GroupID,
GroupName = g.GroupName,
CorrespondingEffect = g.Effect.Effect1
}
).ToList();
GroupIDAndNamesWithCorrespondingEffects
= new ObservableCollection<GroupIDAndNameWithCorrespondingEffect>(
_GroupIDAndNamesWithCorrespondingEffects.Where
(
u => !GetAllChildren(25)
.Select(x => x.GroupID)
.Contains(u.GroupID)
).ToList()
);
In my MainWindow.xaml I have added a resource as follows :
<Window.Resources>
<CollectionViewSource x:Key="GroupNamesWithCorrespondingEffectsCollection" Source="{Binding GroupIDAndNamesWithCorrespondingEffects}" />
</Window.Resources>
Inside Grid's Resources :
<Grid.Resources>
<CompositeCollection x:Key="Items">
<ComboBoxItem IsEnabled="False" Background="#FF2A2A2A" Foreground="White">
<Grid TextElement.FontWeight="Bold" >
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A" />
<ColumnDefinition Width="50" />
<ColumnDefinition SharedSizeGroup="B" />
</Grid.ColumnDefinitions>
<Grid.Children>
<TextBlock Grid.Column="0" Text="Group Name" />
<TextBlock Grid.Column="2" Text="Effect" />
</Grid.Children>
</Grid>
</ComboBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource GroupNamesWithCorrespondingEffectsCollection}}" />
</CompositeCollection>
<DataTemplate DataType="{x:Type self:GroupIDAndNameWithCorrespondingEffect}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A" />
<ColumnDefinition Width="50" />
<ColumnDefinition SharedSizeGroup="B" />
</Grid.ColumnDefinitions>
<Grid.Children>
<TextBlock Grid.Column="0" Text="{Binding GroupName}" />
<TextBlock Grid.Column="2" Text="{Binding CorrespondingEffect}" />
</Grid.Children>
</Grid>
</DataTemplate>
</Grid.Resources>
And I changed the ItemsSource of ComboBox to ItemsSource="{DynamicResource Items}".
Problems :
When I run the program ComboBox displays all the items correctly. Also, two columns with headers are displayed. Its working fine, but when I press Enter or TAB, then focus remains in the same cell and the comboBox's text displays namespace of GroupIDAndNameWithCorrespondingEffect
Here is the image of Problem :
Sample:
Incase if somebody wants to check the sample then it is available here. And database files are available here.
Got it!!!!
I declared the resource with key="Items" in Grid. So, when I Check for ComboBox.SelectedIndex in PreviewKeyDown event of DataGrid, it gives me -1 and so my logic works as unexpected. Also, at this time I get ComboBox.Items.Count = 0.
So I just changed the place of declaration of the resource. I mean I deleted Grid.Resources Section and in ComboBox.Resources section I wrote the same code. Now it is working fine. Now in PreviewKeyDown of DataGrid, I get the expected SelectedIndex of the ComboBox as well as ComboBox.Items.Count is equal to the Count in Source.
I don't know why this happens? As I have used it as DynamicResource I expect it to work even if it is declared in Grid.Resources section.
Maybe ... maybe your combobox just needs IsEditable = false. IsEditable = True causes comboboxes to show namespaces instead of the text.
One way to solve this is in http://www.shujaat.net/2010/08/wpf-editable-combobox-with-datatemplate.html
TextSearch.TextPath="GroupName"
Another way would be to provide a DataTemplate to the ComboBox's ItemTemplate
<ComboBox ItemTemplate="{StaticResource myDataTemplate}"/>