I'm not using a listbox and data binding at the moment, but is it possible to have a listbox work like a carousel and if so, how.
This is what I'm using at the moment, which only works for adding images, not through binding in a listbox... can it still be modified to position each binded canvas+image in the suggested answer?
// add images to the stage
public void addImages()
{
var itemCollection = GalleryModel.DocItemCollection;
foreach (var item in itemCollection)
{
var url = item.ImageUrl;
var image = new Image
{
Source = new BitmapImage(new Uri(url, UriKind.RelativeOrAbsolute))
};
image.Width = 90;
image.Height = 60;
// add the image
LayoutRoot.Children.Add(image);
// Add template here?
// reposition the image
posImage(image, itemCollection.IndexOf(item));
_images.Add(image);
var containingWidth = ActualWidth;
var numberofItemsShown = containingWidth/100;
if (itemCollection.IndexOf(item) < Math.Ceiling(numberofItemsShown)-1)
moveIndex(1);
}
}
// move the index
private void moveIndex(int value)
{
_target += value;
_target = Math.Max(0, _target);
_target = Math.Min(_images.Count - 1, _target);
}
// reposition the image
private void posImage(Image image , int index){
double diffFactor = index - _current;
double left = _xCenter - ((IMAGE_WIDTH + OFFSET_FACTOR) * diffFactor);
double top = _yCenter;
image.SetValue(Canvas.LeftProperty, left);
image.SetValue(Canvas.TopProperty, top);
}
You'd typically use a ListBox for scenarios like this.
The XAML for it would look something like this:
<ListBox x:Name="ImageGalleryListBox">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<tkt:WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding MyImageItemUri}" Margin="8" Width="100" Height="100" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You can of course template it further to make things look the way you want.
In the code-behind here or in your view model, you'd create class that has an MyImageItemUri property and add instances of it to an ObservableCollection<T>. You can then bind or set the collection to the ItemsSource of the ImageGalleryListBox. You'd create more images dynamically by simply adding more of your image items to the observable collection.
Related
I add images from the filedialog in my programm. i want to kow how i can give them attribute like select_event on click for example to remove one.
Thanks in advance
XAML Code .
<Grid DockPanel.Dock="Top" Margin="5,5,5,5" Background="#FFA59A9A">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<WrapPanel Name="Picture_Holder" HorizontalAlignment="Center" Orientation="Horizontal"/>
</ScrollViewer>
</Grid>
C# code
OpenFileDialog OpenFile = new OpenFileDialog();
OpenFile.Multiselect = true;
OpenFile.Title = "Select Picture(s)";
OpenFile.Filter = "ALL supported Graphics| *.jpeg; *.jpg;*.png;";
if (OpenFile.ShowDialog() == true)
{
foreach(String file in OpenFile.FileNames)
{
Add_Image(file);
}
}
private void Add_Image(string file)
{
Console.WriteLine("Une image"+file);
Image new_img = new Image();
new_img.Source = new BitmapImage(new Uri(file));
Thickness img_thickness = new Thickness();
img_thickness.Bottom = 2;
img_thickness.Left = 2;
img_thickness.Right = 2;
img_thickness.Top = 2;
new_img.Margin = img_thickness;
new_img.MaxWidth = new_img.MaxHeight = 105;
Picture_Holder.Children.Add(new_img);
}
You should use a ListBox like shown below, because it has built-in support for item selection. The WrapPanel would go into the ItemsPanel property of the ListBox.
<ListBox x:Name="imageListBox" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" MaxWidth="105" MaxHeight="105" Margin="2"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then assign a collection of file path strings to its ItemsSource property:
imageListBox.ItemsSource = OpenFile.FileNames;
You can now get the file path of the selected image by the SelectedItem property of the ListBox.
In order to use BitmapImage items instead of strings - and thus get a BitmapImage as SelectedItem - write:
imageListBox.ItemsSource = OpenFile.FileNames
.Select(path => new BitmapImage(new Uri(path)));
The next step would be to have a view model with a property that holds the image collection:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ImageSource> Images { get; }
= new ObservableCollection<ImageSource>();
private ImageSource selectedImage;
public ImageSource SelectedImage
{
get { return selectedImage; }
set
{
selectedImage = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(SelectedImage)));
}
}
}
You would assign an instance of the view model to the DataContext property of the MainWindow
DataContext = new ViewModel();
and bind to its properties in XAML:
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Images}"
SelectedItem="{Binding SelectedImage}">
...
</ListBox>
To populate the Images collection in the view model:
var vm = (ViewModel)DataContext;
vm.Images.Clear();
foreach (var path in OpenFile.FileNames)
{
vm.Images.Add(new BitmapImage(new Uri(path)));
}
Remove the selected one by e.g.
vm.Images.Remove(vm.SelectedImage);
which could be executed in an ICommand in the view model.
Added Image Controls to WPF WrapPanel from a list of images defined in xml.
Everything seems to be in place. I even inspected in debug but nothing is visual.
Is there a step I am missing?
_printImages.ReadXml(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"Images.xml"));
if (_printImages.Tables.Contains("image") && _printImages.Tables["image"].Rows.Count > 0)
{
foreach (DataRow row in _printImages.Tables["image"].Rows)
{
// build info object
ImageInfo imgInfo = new ImageInfo();
imgInfo.Source = row["Source"].ToString();
imgInfo.Custom = bool.Parse(row["Custom"].ToString());
imgInfo.Font = row["Font"].ToString();
imgInfo.FontSize = int.Parse(row["FontSize"].ToString());
imgInfo.CharacterLimit = int.Parse(row["Characterlimit"].ToString());
imgInfo.CustomType = row["Customtype"].ToString();
_images.Add(imgInfo);
//create control
Image imgControl = new Image();
BitmapImage imgFile = new BitmapImage();
try
{
imgFile.BeginInit();
imgFile.StreamSource = new FileStream(imgInfo.Source, FileMode.Open);
imgControl.Source = imgFile;
imgControl.Tag = _images.Count - 1;
imgControl.Height = Properties.Settings.Default.ImageHeight;
imgControl.Width = Properties.Settings.Default.ImageWidth;
imgControl.MouseDown += new MouseButtonEventHandler(image_MouseDown);
imgControl.Visibility = System.Windows.Visibility.Visible;
imageSelectionPanel.Children.Add(imgControl);
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message.ToString(), "Unable to create image");
}
}
}
Your code is missing an EndInit call after setting the StreamSource property of the BitmapImage.
Moreover, the stream should be closed after loading the bitmap, which is usually done by a using block and which also requires to set BitmapCacheOption.OnLoad:
using (var stream = new FileStream(imgInfo.Source, FileMode.Open))
{
imgFile.BeginInit();
imgFile.StreamSource = stream;
imgFile.CacheOption = BitmapCacheOption.OnLoad;
imgFile.EndInit();
}
Alternatively, the BitmapImages could also be loaded directly from the image file paths without using a FileStream:
var imgFile = new BitmapImage(new Uri(imgInfo.Source, UriKind.RelativeOrAbsolute));
You might also create a view model with a collection of ImageInfo objects and bind an ItemsControl to this collection. The ItemsControl would have the WrapPanel as its ItemsPanel, and an ItemTemplate with the Image control:
<ItemsControl ItemsSource="{Binding ImageInfos}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Source}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
See the Data Templating Overview article on MSDN for details.
I need to generate a printed form using XAML that has a header grid and a variable number of rows that can result in multiple pages as the number of rows increases. The header must appear on each page and each row may vary in height due to text wrapping within the row. I am currently trying to use ActualHeight of the ItemsControl (rows container) to determine when to generate a new page, but ActualHeight always has a value of zero.
My "XAML_Form" has the following structure. A grid is used in the ItemTemplate to allow aligning columns in the rows with columns in the header grid.
<Grid Width="980" Height="757">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Name="_headerControl" Grid.Row="0"/>
<ItemsControl Name=_rowsControl ItemsSource={Binding Rows} Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Grid>
Our architecture has a report class that handles adding footers, page numbers, and aggregating the pages into a PDF. This report class has a nested ViewModel for each XAML View page. The ViewModel for each page uses a backing List of row objects:
List<RowClass> RowsList;
The ViewModel also has an ICollectionView that is used to bind as the ItemsSource:
ICollectionView Rows = new ListCollectionView(RowsList);
My report class has a CreatePages method which contains code like this:
IList<XAML_Form> pages = new List<XAML_Form>();
var vm = new PageViewModelClass();
var page = new XAML_Form { DataContext = vm };
page.InitializeComponent();
page.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity);
page.Arrange(new Rect(new Point(0,0), page.DesiredSize));
var maxRowsHeight = page.DesiredSize.Height - page._headerControl.ActualHeight;
pages.Add(page);
var rowsOnPage = 0;
foreach (var row in sourceRowsObjectList)
{
rowsOnPage++;
vm.RowsList.Add(row);
vm.Rows.Refresh();
page.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity);
page.Arrange(new Rect(new Point(0,0), page.DesiredSize));
if (page._rowsControl.ActualHeight <= maxRowsHeight)
continue;
// The rows exceed the available space; the row needs to go on the next page.
vm.RowsList.RemoveAt(--rowsOnPage);
vm = new PageViewModelClass();
vm.RowsList.Add(row);
rowsOnPage = 1;
page = new XAML_Form { DataContext = vm };
page.InitializeComponent();
pages.Add(page);
}
return pages;
The initial Measure/Arrange does provide me with the expected value for maxRowsHeight. And the generated form looks fine for a single page with a few rows. My specific problem is: Why is page._rowsControl.ActualHeight always zero? And generally, is there a better approach to this problem?
Here is a solution. We are trying to separate the view concerns from the view model concerns, so there are still improvements to be made.
The CreatePages method in the report class is now:
private static IEnumerable<XAML_Form> CreatePages()
{
IList<XAML_Form> pages = new List<XAML_Form>();
int rowCount = sourceRowsObjectList.Count;
int remainingRowCount = rowCount;
do
{
var pageVm = new PageViewModelClass();
var page = new XAML_Form(pageVm);
pages.Add(page);
int numberOfRowsToAdd = Math.Min(remainingRowCount, XAML_Form.MaxNumberOfRows);
pageVm.AddRows(sourceRowsObjectList.Skip(rowCount - remainingRowCount).Take(numberOfRowsToAdd));
remainingRowCount -= numberOfRowsToAdd;
while (page.AreRowsOverflowing())
{
pageVm.RemoveLastRow();
remainingRowCount++;
}
} while (remainingRowCount > 0);
return pages;
}
The pertinent XAML_Form code behind is as follows:
private static int _maxNumberOfRows = -1;
public XAML_Form(PageViewModelClass viewModel)
{
InitializeComponent();
Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
Arrange(new Rect(new Point(0, 0), DesiredSize);
ViewModel = viewModel;
}
public PageViewModelClass ViewModel
{
get { return (PageViewModelClass)DataContext; }
private set { DataContext = value; }
}
public static int MaxNumberOfRows
{
get // Compute this only once, the first time it is called.
{
if (_maxNumberOfRows < 0) return _maxNumberOfRows;
var page = new XAML_Form();
var singleRowCollection = new object[] { null; }
page._rowsControl.ItemsSource = singleItemCollection;
page._rowsControl.UpdateLayout();
var rowHeight = page._rowsControl.ActualHeight;
_maxNumberOfRows = (int)((page.DesiredSize.Height - page._headerControl.ActualHeight) / rowHeight);
page._rowsControl.ItemsSource = null;
return _maxNumberOfRows;
}
}
// Call this method as rarely as possible. UpdateLayout is EXPENSIVE!
public bool AreRowsOverflowing()
{
_rowsControl.UpdateLayout();
return _rowsControl.ActualHeight > DesiredSize.Height - _headerControl.ActualHeight;
}
I have two wpf tool kit charts one is pie chart and second bar series
i have method which i call only on form load
chartGuest.DataContext = null;
List<KeyValuePair<string, int>> valueList = new List<KeyValuePair<string, int>>();
HProDataContext db = new HProDataContext();
var _RoomTypes = (from d in db.roomtypes select d.roomtype1).ToList();
var _RoomTypeID = (from d in db.roomtypes select d.id).ToList();
int count = 0;
for (int i = 0; i < _RoomTypeID.Count; i++)
{
count = Convert.ToInt32((from d in db.actions where d.room.roomtypeid == _RoomTypeID[i] select d.id).Count());
valueList.Add(new KeyValuePair<string, int>(_RoomTypes[i], count));
}
chartGuest.DataContext = valueList;
It gaves such error : Cannot modify the logical children for this node at this time because a tree walk is in progress.
The same code works great on pie series chart.
This is my charts:
<charting:Chart x:Name="chartRoomType" Width="402" Height="255" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="15,275,0,0">
<charting:Chart.Series>
<charting:PieSeries ItemsSource="{Binding}" DependentValuePath="Value" IndependentValuePath="Key" Title="Room Types" IsSelectionEnabled="True" />
</charting:Chart.Series>
</charting:Chart>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="314,292,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<TextBlock Height="23" HorizontalAlignment="Left" Margin="28,296,0,0" Name="textBlock4" Text="Room Types" VerticalAlignment="Top" />
<charting:Chart x:Name="chartGuest" Height="269" VerticalAlignment="Top" Margin="6,0" Title="Guests">
<charting:Chart.Series>
<charting:BarSeries ItemsSource="{Binding}" DependentValuePath="Value" IndependentValuePath="Key" Title="Room Types" IsSelectionEnabled="True" />
</charting:Chart.Series>
</charting:Chart>
Can anyone help me?
P.s. i found this question but it was unhelpful
What does Cannot modify the logical children for this node at this time because a tree walk is in progress mean?
Use DataPointSeries.ItemsSource to bind to the data context.
Assuming that this is XAML of your your window panel that has the datagrid and charting control sharing a common list as ItemsSource..
<StackPanel>
<tk:DataGrid MaxHeight="200" AutoGenerateColumns="False"
ItemsSource="{Binding}"
IsReadOnly="True">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Key"
Binding="{Binding Key, Mode=OneWay}"/>
<tk:DataGridTextColumn Header="Value"
Binding="{Binding Value, Mode=OneWay}"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
<charting:Chart MaxHeight="300"
Title="Title"
LegendTitle="Legend"
Name="Chart1">
<charting:AreaSeries DependentValuePath="Value"
IndependentValuePath="Key"
Background="Red" >
<charting:DataPointSeries.ItemsSource>
<Binding BindsDirectlyToSource="True"/>
</charting:DataPointSeries.ItemsSource>
</charting:AreaSeries>
</charting:Chart>
<Button Content="Change DataGrid and Chart Data" Click="Button_Click"/>
</StackPanel>
In code behind we reset the data context of the window ....
private List<KeyValuePair<int, int>> list1;
public Window1()
{
InitializeComponent();
list1 = new List<KeyValuePair<int, int>>();
var random = new Random();
for(int i = 0; i < 1000; i++)
{
list1.Add(new KeyValuePair<int, int>(i, random.Next()));
}
this.DataContext = list1;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
list1 = new List<KeyValuePair<int, int>>();
var random = new Random();
for (int i = 0; i < 1000; i++)
{
list1.Add(new KeyValuePair<int, int>(i, random.Next()));
}
this.DataContext = list1;
}
Everytime you click the button the chart refreshes without error.
Let me know if this helps.
I've received the same exception in different situation. I work with code which takes collection of existing UIElements and create new UIElement and set first one as Content of second one. Aditionally, it takes property value from existing UIElement and set it as value of different property of new UIElement.
foreach(UIElement insideElement in uiElementCollection)
{
var outsideElement = new TabItem
{
Content = insideElement
,Header = insideElement.Title
}
}
But in this case Title of inside property can be a Binding, but code above copies only value and when insideElement.Title is bounded to some source, outsideElement.Header doesn't reflect change.
So I changed code to bind HeaderProperty to TitleProperty of insideElement:
foreach(UIElement insideElement in uiElementCollection)
{
var outsideElement = new TabItem
{
Content = insideElement
}
outsideElement.SetBinding(HeaderedContentControl.HeaderProperty,
new Binding
{
Source = insideElement,
Path = new PropertyPath(MyUIElement.TitleProperty)
});
}
But when I executed code above, I received
System.InvalidOperationException: Cannot modify the logical children for this node at this time because a tree walk is in progress.
I found that I can't bind one dependency property to another one, which is stored inside Content property.
I tried to get binding expression of innerUIElement and set it as new binding to HeaderProperty of outsideUIElement.
foreach(UIElement insideElement in uiElementCollection)
{
var outsideElement = new TabItem
{
Content = insideElement
}
BindingExpression titleBindingExpression = pageControl.GetBindingExpression(MyUIElement.TitleProperty);
if (titleBindingExpression != null)
{
tabItem.SetBinding(HeaderedContentControl.HeaderProperty, new Binding { Source = titleBindingExpression.DataItem, Path = titleBindingExpression.ParentBinding.Path });
}
else
{
tabItem.Header = innerUIElement.Title;
}
}
Now, outer UI Element is binded to same source and WPF presenter doesn't throw Exception. My problem is solved.
I know this has been a while, but in case anyone else comes across it I found another way around it. Put the chart into a StackPanel, created with Visibility of "Collapsed" ("Hidden" DOES NOT WORK). Then set visibility to "Visible" after setting DataContext the first time (I'm doing it every time, but after the first it's redundant).
My experience agrees with reports I've seen that the problem is a bug that occurs when setting a non-empty DataContext after having had an empty one; but apparently it doesn't do the "tree walk" if the chart is collapsed.
I am creating a set of images dynamically and putting them into a Stack Panel like this :-
Image image = new Image();
image.Name = "image_" + iCounter;
image.Height = 100;
image.Width = 100;
image.Source = bitmap;
image.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
image.Stretch = Stretch.Fill;
image.VerticalAlignment = VerticalAlignment.Top;
//image.MouseDown += new MouseButtonEventHandler(image_MouseDown);
image.ToolTip = "Right-Click for Options";
image.ContextMenu = GetContextMenu();
Separator separator = new Separator();
separator.Name = "separator_" + iCounter;
AddImagesStackPanel.Children.Add(image);
AddImagesStackPanel.Children.Add(separator);
iCounter++;
Then in the Context Menu I have this code :-
private System.Windows.Controls.ContextMenu GetContextMenu()
{
System.Windows.Controls.MenuItem mi1;
System.Windows.Controls.MenuItem mi2;
System.Windows.Controls.ContextMenu _contextMenu = new System.Windows.Controls.ContextMenu();
mi1 = new System.Windows.Controls.MenuItem();
mi1.Header = "Show Normal Size";
mi1.Click += new RoutedEventHandler(ContextMenuItem1_Click);
mi2 = new System.Windows.Controls.MenuItem();
mi2.Header = "Remove image";
mi2.Click += new RoutedEventHandler(ContextMenuItem2_Click);
_contextMenu.Items.Add(mi1);
_contextMenu.Items.Add(mi2);
return _contextMenu;
}
Now I wish to get the selected item when the user right clicks on an image and I have this code :-
private void ContextMenuItem2_Click(object sender, RoutedEventArgs e)
{
object obj = e.OriginalSource;
string imageName = ((System.Windows.Controls.Image)obj).Name;
string[] split = imageName.Split('_');
imageUploads.RemoveAt(Convert.ToInt32(split[1]));
DisplayImagesInStackPanel(imageUploads);
}
But obj does not contain the name of the image since its a RoutedEventArgs. Is there any way I can get the selected item in the context menu?
After discussing this in the comments this should work:
// The binding source.
private readonly ObservableCollection<BitmapImage> _imageList = new ObservableCollection<BitmapImage>();
public ObservableCollection<BitmapImage> ImageList
{
get { return _imageList; }
}
How to display this and set up the ContextMenu:
<ItemsControl ItemsSource="{Binding ImageList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding}" Width="100" Height="100"
HorizontalAlignment="Left" Stretch="Fill"
VerticalAlignment="Top" ToolTip="Right-Click for Options">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Show Normal Size" Click="Image_CM_ShowNormalSize_Click"
Tag="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget}"/> <!-- The placement target is the object to which the context menu belongs, i.e. the image -->
<MenuItem Header="Remove Image" Click="Image_CM_RemoveImage_Click"
Tag="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.DataContext}"/> <!-- The DataContext of the Image is the BitmapImage, which should be removed from the list -->
</ContextMenu>
</Image.ContextMenu>
</Image>
<Separator/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
What the handlers might look like:
private void Image_CM_ShowNormalSize_Click(object sender, RoutedEventArgs e)
{
Image img = (sender as FrameworkElement).Tag as Image;
img.Width = (img.Source as BitmapImage).PixelWidth;
img.Height = (img.Source as BitmapImage).PixelHeight;
}
private void Image_CM_RemoveImage_Click(object sender, RoutedEventArgs e)
{
BitmapImage img = (sender as FrameworkElement).Tag as BitmapImage;
// If the image is removed from the bound list the respective visual elements
// will automatically be removed as well.
ImageList.Remove(img);
}
But obj does not contain the name of the image since its a RoutedEventArgs.
True, but the obj at that point is a MenuItem, if you drill one level down, you can get the image.
Is there any way I can get the selected item in the context menu?
Normally one would load the model classes (Image in your case) through the binding of ItemSource of the Menu (or MenuItem if they are to be submenus) and if one takes that route they can pull the originating item off of the DataContext such as in my case the item was an MRU class item.
private void SelectMRU(object sender, RoutedEventArgs e)
{
var mru = (e.OriginalSource as MenuItem).DataContext as MRU;
var name = mru.Name;
...
}
Because you do things by hand you should load the Tag property of the MenuItem with the Image in question
mi1.Tag = {Image instance in question};
then extract on the event.
var image = (e.OriginalSource as MenuItem).Tag as Image;