Clone Grid Windows Phone 8 - c#

I have a Grid template in XAML CODE:
<Grid x:name="gridTemplate>
Childrens...
</Grid>
Now I want to place this grid in LongListSelector in foreach loop:
foreach(var item in myList)
{
clonedGrid= ??? (need clone here my xaml control)
longlistselector.Items.Add(clonedGrid):
}
This works for me for WPF:
public static class ExtensionMethods
{
public static T XamlClone<T>(this T original)
where T : class
{
if (original == null)
return null;
object clone;
using (var stream = new MemoryStream())
{
XamlWriter.Save(original, stream);
stream.Seek(0, SeekOrigin.Begin);
clone = XamlReader.Load(stream);
}
if (clone is T)
return (T)clone;
else
return null;
}
}
How to implement this in WINDOWS PHONE 8?

I would make a contentControl, where I could put dataTemplate from resources like this:
xaml
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="MyGrid">
<Grid>
<!-- here is your data template, where you can bind to item's properties -->
</Grid>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
cs
foreach (var item in myList)
{
ContentControl control = new ContentControl();
control.Content = item;
control.ContentTemplate = Resources["MyGrid"] as DataTemplate;
longlistselector.Items.Add(control);
}

If you Grid does not have many properties set, then just create method that creates new Grid and pass it as argument your old Grid so you can set all the properties to be same.
public Grid CloneGrid(Grid input)
{
Grid temp = new Grid();
temp.Width = input.Widht;
... etc
return temp;
}
EDIT:
Another way is to define your properties as Style in App.xaml and then apply it to the Grid:
Grid.Style = App.Current.Resources[StyleKey] as Style;

Related

WPF TemplateSelector in custom control - usage in code

In my MVVM project, I had to create a custom control RowGrid inheriting from grid.
This control has an ItemsSource and an ItemsTemplateSelector.
(I am not using an ItemsControl, because I need to set a a relative size for each child, and I am doing it by setting the column-widths to x*)
I am trying to assign the template with the ItemsTemplateSelector in code, but it does not work properly:
Children.Clear();
int i = 0;
foreach (var element in ItemsSource)
{
if (element != null)
{
DataTemplate dataTemplate = ItemTemplateSelector.SelectTemplate(element, null);
ContentControl contentControl = new ContentControl
{
DataContext = element,
ContentTemplate = dataTemplate
};
Children.Add(contentControl);
SetColumn(contentControl, i);
}
i++;
}
the ItemTemplateSelector.SelectTemplate is a simple switch/case where, depending on the type of element, a specific DataTemplate is returned.
A DataTemplate example is the following:
<DataTemplate x:Key="StringTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Name}" Grid.Column="0"></Label>
<customControls:MyStringTextBox MyString="{Binding}" Grid.Column="1"/>
</Grid>
</DataTemplate>
If instead of my RowGrid custom control, I use a ItemsControl, the bindings of the DataTemplate work.
If I use my custom control, they do not.
This means that the ItemsSource is fine, the ItemsTemplateSelector is fine and the DataTemplate is fine.
The issue is how I am putting together DataTemplate and its ViewModel
What am I doing wrong?
What am I missing?
Thank you for any support!
I do not really like it, but I found a solution:
I initialize the contentControl this way:
ContentControl contentControl = MergeTemplateWithContext(dataTemplate, element)
plus
public static ContentControl MergeTemplateWithContext(DataTemplate template, object context)
{
ContentControl contentControl = new ContentControl
{
ContentTemplate = template
};
contentControl.Loaded += (object sender, RoutedEventArgs e) =>
{
if (VisualTreeHelper.GetChildrenCount(contentControl) > 0)
{
DependencyObject child = VisualTreeHelper.GetChild(contentControl, 0);
if (child is ContentPresenter contentPresenter && VisualTreeHelper.GetChildrenCount(contentPresenter) > 0)
{
DependencyObject grandChild = VisualTreeHelper.GetChild(contentPresenter, 0);
if (grandChild is FrameworkElement frameworkElement)
{
frameworkElement.DataContext = context;
}
}
}
} ;
return contentControl;
}

WPF Add trigger to an existing style from code behind

I have a TabControl with some tabs declared in XAML. I want to add new tabs and bind their IsEnabled properties to some properties of their content:
for (int i = 0; i < context.Pictures.Count; ++i)
{
var tabItem = new TabItem();
var title = "Some title"
tabItem.Header = title;
var image = new Image();
Binding sourceBinding = new Binding(nameof(context.Pictures) + $"[{i}]");
sourceBinding.Source = context;
image.SetBinding(Image.SourceProperty, sourceBinding);
image.Width = 800;
image.Height = 600;
DataTrigger isEnabledTrigger = new DataTrigger() { Binding = sourceBinding, Value = null };
isEnabledTrigger.Setters.Add(new Setter(TabItem.IsEnabledProperty, false));
tabItem.Content = image;
tabControl.Items.Add(tabItem);
}
I want to disable tab if the picture inside is null (apply isEnabledTrigger). Problem here is that style of tabItem is derived from tabControl containing it, so I cannot just create a style with my trigger and apply it to TabItem. Sure, I could just copy original style and hardcode it, but I don't think it's a good way to solve my problem.
So, to solve my problem I have two ideas:
Create a shallow copy of existing style, add trigger and apply it
Load original style from XAML, add trigger and apply it (may be difficult, since it lies in another project)
Is there more rational way to bind TabControls IsEnabled to contained Images value?
Don't add TabItem directly. Use data models. This is recommended approach for all item controls. Then define a DataTemplate for the data model(s) and assign it to TabControl.ContentTemplate.
Use the TabControl.ItemTemplate to layout the header.
Defining a Style for the TabControl.ItemContainerStyle allows you to set up the required triggers quite easily. Doing layout using C# is never a good idea. Always use XAML.
See: Data binding overview in WPF, Data Templating Overview
The minimal model class should look like this:
PictureModel.cs
// All binding source models must implement INotifyPropertyChanged
public class PictureModel : INotifyPropertyChanged
{
private string title;
public string Title
{
get => this.title;
set
{
this.title = value;
OnPropertyChanged();
}
}
private string source;
public string Source
{
get => this.source;
set
{
this.source = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<PictureModel> Pictures { get; }
private void CreateTabItems(Context context)
{
foreach (string imageSource in context.Pictures)
{
var pictureModel = new PictureModel()
{
Title = "Some Title",
Source = imageSource
};
this.Pictures.Add(pictureModel);
}
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<!-- Layout the tab content -->
<TabControl ItemsSource="{Binding Pictures}">
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type viewModels:PictureModel}">
<Image Source="{Binding Source}" />
</DataTemplate>
</TabControl.ContentTemplate>
<!-- Layout the tab header -->
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:PictureModel}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Setup triggers. The DataContext is the current data model -->
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Source}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Window>
You should base your Style on the current Style:
Style style = new Style(typeof(TabItem))
{
BasedOn = FindResource(typeof(TabItem)) as Style
};
DataTrigger isEnabledTrigger = new DataTrigger() { Binding = sourceBinding, Value = null };
isEnabledTrigger.Setters.Add(new Setter(TabItem.IsEnabledProperty, false));
style.Triggers.Add(isEnabledTrigger);
tabItem.Style = style;
Or
Style style = new Style(typeof(TabItem))
{
BasedOn = tabControl.ItemContainerStyle
};
...
...depending on how your current Style is applied.
This is how you would extend an existing Style with your DataTrigger and this is a good way of solving this.

WPF accessing scrollviewer of a listview codebehind

I need to access the scrollviewer of a listview from the codebehind.
here is the definition of my listview
<ListView Grid.Row="1" ItemsSource="{Binding Path=SpecList, UpdateSourceTrigger=PropertyChanged}"
Name="mylistview"
ItemTemplate="{StaticResource SpecElementTemplate}"
Background="{StaticResource EnvLayout}"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ItemContainerStyle="{StaticResource MyStyle}"
BorderBrush="Blue"
BorderThickness="20"
Margin="-2">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
How can I get the scrollviewer?
Thank you
Andrea
There are several ways to get the ScrollViewer. Simplest solution is to get the the first child of the first child of the ListView. This means get the Border and the ScrollViewer inside this Border like described in
this answer:
// Get the border of the listview (first child of a listview)
Decorator border = VisualTreeHelper.GetChild(mylistview, 0) as Decorator;
// Get scrollviewer
ScrollViewer scrollViewer = border.Child as ScrollViewer;
A second way is to scan all childrens recursive to find the ScrollViewer. This is described in the answer by Matt Hamilton in this question. You can simply use this function to get the ScrollViewer.
ScrollViewer scrollViewer = GetChildOfType<ScrollViewer>(mylistview);
This second solution is much more generic and will also work if the template of your ListView was edited.
Use VisualTreeHelper class to access any child control.
Psudeo code to your case:
//Declare a scroll viewer object.
ScrollViewer sViewer = default(ScrollViewer );
//Start looping the child controls of your listview.
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(YOUR_LISTVIEW_OBJECT.VisualParent ); i++)
{
// Retrieve child visual at specified index value.
Visual childVisual = (Visual)VisualTreeHelper.GetChild(YOUR_LISTVIEW_OBJECT.VisualParent , i);
ScrollViewer sViewer = childVisual as ScrollViewer;
//You got your scroll viewer. Stop looping.
if (sViewer != null)
{
break;
}
}
I also suggest using the CollectionChanged event. In this code, the CollectionChanged event handler is added to the codebehind after the view model has been loaded. Then, each time the collection changes we scroll to the bottom of the listview. Here is an important point. The scrollviewer child of the list view might not yet be completely rendered when our events start firing. Hence we will get exceptions if we try to use the VisualTreeHelper.GetChild method. So, we have to first attempt to get the scrollviewer and then ignore its positioning if it is not yet available.
private void ReceivedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Make sure the items source property in the viewmodel has some items
if (myViewModel.ReceivedItems.Count > 0)
{
var aScrollViewer = RcvdListView.GetChildOfType<ScrollViewer>();
// Make sure the scrollviewer exists before trying to position it
if (aScrollViewer != null)
{
aScrollViewer.ScrollToBottom();
}
}
}
Listview's ScrollViewer should be accessible after LayoutUpdated. You could hook on LayoutUpdated and then get if from Visual tree
private static void ListView_LayoutUpdated(object sender, EventArgs e)
{
var listview = (ListView)sender;
var viewer = listview.GetFirstChildOfType<ScrollViewer>();
}
public static T GetFirstChildOfType<T>(this DependencyObject dependencyObject) where T : DependencyObject
{
if (dependencyObject == null)
{
return null;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
var result = (child as T) ?? GetFirstChildOfType<T>(child);
if (result != null)
{
return result;
}
}
return null;
}

WPF MVVM combine two collections in one view model

I'm struggling with something and hope somebody can help with a tip on how to accomplish this:
I wrote a UserControl that looks like a File Explorer with on the left side a TreeView with diskdrives and folders (each item has a checkbox) and as soon as a TreeViewItem on the left side is selected, the right side should display the contents of that directory in a ListView, also with a checkbox for each file.
The TreeView is bound in a ViewModel and has many children and subchildren. This works well. The right side however, should only be 1 level of children and no subchildren.
I get the "IsSelected" event within the ViewModel, but of course I then have a reference to the current selected TreeViewItem on the left side, which is totally unrelated to the main level ListView on the right side, which I need to fill with data at that point. How can I reach the main level ListView on the right side, is there any way to create a global lvMain or something that I can access from within the ViewModel or am I appoaching all of this the wrong way and do I need 2 ViewModels instead? Any tips are welcome!
Edit: Here is an image of the desired result, it is the non MVVM version of it, which I am now converting to a control that uses a viewmodel in order to add extra functionality:
enter image description here
Some XAML code snippets:
<TreeView Name="myTreeView" ItemsSource="{Binding tvChildren}" ItemTemplate="{StaticResource CheckBoxItemTemplate}" Height="Auto" Width="Auto"></TreeView>
...
<ListView x:Name="myListView" FontWeight="Normal" ItemsSource="{Binding lvChildren}" Width="Auto" HorizontalAlignment="Stretch">
UserObject code snippets:
public FileExplorer()
{
InitializeComponent();
try
{
// Initially fill TreeView (left side of FileExplorer) with local drives:
DriveInfo[] drives = DriveInfo.GetDrives();
ViewModel dummyNode = null;
int i = 0;
foreach (DriveInfo drive in drives)
{
if (drive.DriveType != DriveType.CDRom)
{
ViewModel node = new ViewModel();
tvMainNode.tvChildren.Add(node);
node.Text = drive.Name;
node.FullDir = drive.Name;
node._parent = tvMainNode;
// Add a blanc child so the toggle buttons are generated:
tvMainNode.tvChildren[i].tvChildren.Add(dummyNode);
i++;
}
}
}
catch { return; }
DataContext = this;
}
ViewModel code snippet:
private readonly ObservableCollection<ViewModel> _tvChildren = new ObservableCollection<ViewModel>();
private readonly ObservableCollection<ViewModel> _lvChildren = new ObservableCollection<ViewModel>();
void SetIsSelected(bool value)
{
if (value == _isSelected) { return; }
_isSelected = value;
// This is obviously not working:
ViewModel test = new ViewModel();
this.lvChildren.Clear();
test.Naam = "bla";
this.lvChildren.Add(test);
}

Binding to Listbox programmatically in silverlight

I want a listbox that will show all the images and text "layers" that I have on my Canvas in silverlight. The code I have currently crashes when I try to view the listbox or when I'm viewing the listbox when I add an element. I can't figure out why. Can someone point me in the right direction with this?
XML -
<Grid DataContext="{Binding Path=Project}">
...
...
<TextBlock Name="textBlock1" Text="Layers" Margin="18,16,0,0" />
<StackPanel Grid.Row="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
<ListBox ItemsSource="{Binding Path=Elements}" Height="175" Name="listBox1" Width="172"/>
</StackPanel>
</Grid>
Project.cs
//List of elements
private ObservableCollection<FrameworkElement> elements;
public ObservableCollection<FrameworkElement> Elements
{
get { return elements; }
set
{
elements = value;
NotifyPropertyChanged("Elements");
}
}
// An example of how an element is added to the Elements collection
// There are also image elements added similarly
private void AddTextElement(object param)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = "New Text";
textBlock.Foreground = new SolidColorBrush(Colors.Gray);
textBlock.FontSize = 25;
textBlock.FontFamily = new FontFamily("Arial");
textBlock.Cursor = Cursors.Hand;
textBlock.Tag = null;
this.Elements.Add(textBlock);
numberOfElements++;
this.SelectedElement = textBlock;
this.selectedTextElement = textBlock;
}
private void AddImageElement(object param)
{
bool? gotImage;
string fileName;
BitmapImage imageSource = GetImageFromLocalMachine(out gotImage, out fileName);
if (gotImage == true)
{
Image image = new Image();
OrderElements(image);
image.Name = fileName;
image.Source = imageSource;
image.Height = imageSource.PixelHeight;
image.Width = imageSource.PixelWidth;
image.MaxHeight = imageSource.PixelHeight;
image.MaxWidth = imageSource.PixelWidth;
image.Cursor = Cursors.Hand;
image.Tag = null;
AddDraggingBehavior(image);
image.MouseLeftButtonUp += element_MouseLeftButtonUp;
this.Elements.Add(image);
numberOfElements++;
this.SelectedElement = image;
this.SelectedImageElement = image;
}
}
One reason might be, because you bind using Path property in your Grid element.
You should use binding source, and set your Project object as a staticresource which you can point to when you call binding source.
Like this:
<Window
xlmns:local="NamespaceOfMyProject">
<Window.Resources>
<local:Project x:key="MyProjectResource" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource MyProjectResource}}>
....
</Grid>
....
</Window>
Reason is: You use "Source" when you point to objects, and "Path" when you point to properties.
Another way to set the DataContext is to do it in the codebehind, using this C# code. But first give your grid a name, so it can be referenced in the codebehind:
<Grid x:Name="myGrid">
Codebehind:
myGrid.DataContext = new Project();
Working with Images incorrectly will typically cause the crash; Show the code for implementation of your Elements and how you setting the images.
Also your XAML is missing ItemTemplate,where u would set the image and text.
I'd guess it's crashing because you've got FrameworkElements that you've added to a Canvas, but then you're also adding them to your List. FrameworkElements generally don't like being added to the visual tree multiple times.
If this is the problem, something like this might work around it (bind your list to ElementsAsStrings):
private ObservableCollection<FrameworkElement> elements;
public ObservableCollection<FrameworkElement> Elements
{
get { return elements; }
set
{
if(elements != null)
elements.CollectionChanged -= onElementsChanged;
elements = value;
if(elements != null)
elements.CollectionChanged += onElementsChanged;
NotifyPropertyChanged("Elements");
NotifyPropertyChanged("ElementsAsStrings");
}
}
public IEnumerable<string> ElementsAsStrings
{
get
{
foreach(var element in Elements)
{
if(element is TextBox)
yield return (element as TextBox).Text;
// More cases here
}
}
}
private void onElementsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("ElementsAsStrings");
}

Categories