2014-01-08 24 views
3

我以编程方式创建了一些涉及一些重处理的UI。基本上我想要的是当我的UI被构建/添加到窗口时运行加载器动画。 添加的UI是一些网格和一些图像被加载到它们中。WPF UI多任务

到目前为止,我已经尝试过BackgroundWorker,但由于我需要使用UI线程来添加我正在构建的UI,因此加载器将无法启动/添加动画,直到添加的UI完成。

我也尝试了一些没有任何结果的异步方法。 我最后的尝试是这样的事情,但它仍然需要访问UI线程最终将冻结UI动画,直到工作完成

private async void begin() 
    { 

     await this.LongRunOpAsync(); 

    } 

    public Task LongRunOpAsync() 
    { 
     return Task.Run(() => 
     { 
      InitGrid(); 
      stopLoadingScreen(); 
     }); 
    } 

我可能失去了一些东西,但我不知道是什么我也没有想法做到这一点。 任何帮助,将不胜感激。

编辑: 有繁重的工作

private void makeIgrid() 
     { 
      Grid hostgrid = new Grid(); 
      hostgrid.Name = "imagesHostGrid"; 
      hostgrid.Width = 700; 
      hostgrid.VerticalAlignment = VerticalAlignment.Top; 
      hostgrid.HorizontalAlignment = HorizontalAlignment.Center; 
      hostgrid.SetValue(Canvas.ZIndexProperty, 0); 
      this.RegisterName(hostgrid.Name, hostgrid); 

      Grid imagegrid = new Grid(); 
      imagegrid.Name = "imagegrid"; 
      imagegrid.Height = height2; 
      //imagegrid.Width = 700; 
      imagegrid.SetValue(Canvas.ZIndexProperty, 0); 
      imagegrid.VerticalAlignment = VerticalAlignment.Top; 
      imagegrid.HorizontalAlignment = HorizontalAlignment.Center; 
      imagegrid.Margin = new Thickness(0, height1, 0, 0);//(left,top,right,bottom) 

      RowDefinition iRow1 = new RowDefinition(); 
      iRow1.Height = new GridLength(2, GridUnitType.Star); 
      imagegrid.RowDefinitions.Add(iRow1); 

      RowDefinition iRow2 = new RowDefinition(); 
      iRow2.Height = new GridLength(70, GridUnitType.Star); 
      imagegrid.RowDefinitions.Add(iRow2); 

      ScrollViewer sv = new ScrollViewer 
      { 
       CanContentScroll = true, 
       HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden, 
       VerticalScrollBarVisibility = ScrollBarVisibility.Disabled 
      }; 

      for (int i = 0; i < images.Length; i++) 
      { 
       ColumnDefinition columns = new ColumnDefinition(); 
       columns.MinWidth = 100; 
       columns.Width = new GridLength(100, GridUnitType.Star); 
       imagegrid.ColumnDefinitions.Add(columns); 

       BitmapImage bmp = new BitmapImage(); 
       bmp.BeginInit(); 
       bmp.UriSource = new Uri(currentDirectory + "//Media//Images//" + selectedFolder + "//" + System.IO.Path.GetFileName(images[i].ToString()), UriKind.Relative); 
       bmp.CacheOption = BitmapCacheOption.OnLoad; 
       Debug.WriteLine("Loading: " + currentDirectory + "//Media//Images//" + selectedFolder + "//" + System.IO.Path.GetFileName(images[i].ToString())); 
       bmp.EndInit(); 

       Image img = new Image(); 

       img.Name = System.IO.Path.GetFileNameWithoutExtension(images[i].ToString()); 
       img.Source = bmp; 
       img.VerticalAlignment = VerticalAlignment.Center; 
       img.HorizontalAlignment = HorizontalAlignment.Center; 
       img.TouchDown += addImagetoScreen; 
       img.Width = 94; 
       img.Stretch = Stretch.Uniform; 
       img.SetValue(Canvas.ZIndexProperty, 0); 
       this.RegisterName(img.Name, img); 

       Border border = new Border(); 
       border.SetResourceReference(Control.BackgroundProperty, "MenuSelected"); 
       border.SetValue(Canvas.ZIndexProperty, 0); 

       Grid.SetRow(border, 0); 
       Grid.SetColumn(border, i); 

       Grid.SetRow(img, 1); 
       Grid.SetColumn(img, i); 

       imagegrid.Children.Add(border); 
       imagegrid.Children.Add(img); 

      } 
      sv.Content = imagegrid; 
      sv.SetValue(Canvas.ZIndexProperty, 0); 
      hostgrid.Children.Add(sv); 
      mainGrid.Children.Add(hostgrid); 



     } 
+0

您可以做的最好的方法是更改​​区块中的用户界面,以便有时间进行刷新并做出响应。例如一次添加一行。 –

+1

搜索“WPF splashscreen”。可能会给你一些想法。 – JeffRSon

+2

短语*我创建一些用户界面以编程方式*听起来够糟糕(在WPF中)让我要求您发布一个您实际尝试做的示例。 –

回答

16

好的。删除所有的代码并从头开始。

这是如何做到这一点在WPF:

<Window x:Class="WpfApplication14.ItemsControlSample2" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="ItemsControlSample2" WindowState="Maximized"> 
    <ItemsControl ItemsSource="{Binding}"> 
     <ItemsControl.ItemTemplate> 
      <DataTemplate> 
       <Border Background="DarkGray" BorderBrush="Black" BorderThickness="1" CornerRadius="5" 
         Width="100" Height="100" Margin="10" > 
        <Grid> 
         <Image x:Name="img" Source="{Binding ImageSource}" Margin="2"/> 

         <TextBlock x:Name="txt" Text="Loading..." FontWeight="Bold" 
           VerticalAlignment="Center" HorizontalAlignment="Center" 
           Visibility="Collapsed" Foreground="AliceBlue"/> 
        </Grid> 
       </Border> 

       <DataTemplate.Triggers> 
        <DataTrigger Binding="{Binding IsLoading}" Value="True"> 
         <Setter TargetName="img" Property="Source" Value="{x:Null}"/> 
         <Setter TargetName="txt" Property="Visibility" Value="Visible"/> 
        </DataTrigger> 
       </DataTemplate.Triggers> 
      </DataTemplate> 
     </ItemsControl.ItemTemplate> 

     <ItemsControl.Template> 
      <ControlTemplate TargetType="ItemsControl"> 
       <ScrollViewer> 
        <WrapPanel IsItemsHost="True"/> 
       </ScrollViewer> 
      </ControlTemplate> 
     </ItemsControl.Template> 
    </ItemsControl> 
</Window> 

代码背后:

public partial class ItemsControlSample2 : Window 
{ 
    public ItemsControlSample2() 
    { 
     InitializeComponent(); 

     //Make sure you change this path to a valid path in your PC where you have JPG files 
     var path = @"F:\Media\Images\My Drums"; 

     var images = Directory.GetFiles(path,"*.jpg") 
           .Select(x => new ImageViewModel() 
              { 
               Path = x, 
              }); 
     DataContext = images.ToList(); 
    } 
} 

数据项:

public class ImageViewModel : INotifyPropertyChanged 
{ 
    private bool _isLoading; 
    public bool IsLoading 
    { 
     get { return _isLoading; } 
     set 
     { 
      _isLoading = value; 
      OnPropertyChanged("IsLoading"); 
     } 
    } 

    private ImageSource _imageSource; 
    public ImageSource ImageSource 
    { 
     get { return _imageSource; } 
     set 
     { 
      _imageSource = value; 
      OnPropertyChanged("ImageSource"); 
     } 
    } 

    private string _path; 
    public string Path 
    { 
     get { return _path; } 
     set 
     { 
      _path = value; 
      OnPropertyChanged("Path"); 

      LoadImageAsync(); 
     } 
    } 

    private void LoadImageAsync() 
    { 
     IsLoading = true; 

     var UIScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

     Task.Factory.StartNew(() => 
     { 
      var bmp = new BitmapImage(); 
      bmp.BeginInit(); 
      bmp.UriSource = new Uri(Path, UriKind.Relative); 
      bmp.CacheOption = BitmapCacheOption.OnLoad; 
      bmp.EndInit(); 
      bmp.Freeze(); 
      return bmp; 
     }).ContinueWith(x => 
     { 
      ImageSource = x.Result; 
      IsLoading = false; 
     },UIScheduler); 
    } 

    #region INotifyPropertyChanged 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 
    } 
    #endregion 
} 

结果:

enter image description here

  • 注意我是如何声明定义的UI在XAML,而不是程序上在C#代码创建它。这是一个更清洁的方法,因为它可以让WPF做它的工作

  • 我使用的是ItemsControl这是适用于WPF中所有基于“项目”的UI的适用方法。无论他们的视觉外观如何。

  • 另请注意,我如何利用DataBinding以便用实际数据填充UI,还需要DataTriggers以创建基本的有状态行为。

    • 在图像被加载(IsLoading == true),则Image.Source为null,并且示出了TextBlock
    • 当图像完成加载(IsLoading == false)时,Image.Source被绑定到ViewModel数据,而TextBlock被隐藏。
  • 看看我如何使用WrapPanel进行布局,而不是手动放置物品。这给你一个“类似资源管理器”的行为。尝试调整窗口大小以查看结果。

  • 我强烈建议你阅读上面的链接文档,主要是ItemsControl的东西,还有Rachel的WPF Mentality文章。

  • WPF Rocks。只需将我的代码复制并粘贴到File -> New Project -> WPF Application中即可自行查看结果。

  • 我使用C#4.0和.Net 4.0,所以我没有async/await。你应该能够删除所有基于Task的代码,并用这个更新,更清晰的异步方法取代它。

  • 让我知道你是否需要进一步的帮助。

+0

感谢帮帮我。我仍然是WPF新手,所以我仍然在学习分配。正如你所说的WPF Rocks,它非常容易理解,并允许我们做非常好的事情。我想要做的事情是不可能的,所以我把你的答复标记为答案,因为你解释得很好。感谢所有帮助过的人:)。我可能很快就会要求你们所有人提供帮助 – user3091405

+2

在'bmp.BeginInit();'后面加'bmp.DecodePixelWidth = 94;'保存内存并加速显示 – Maxence

+0

嗨,我得到的错误可冻结的对象不能被冻结 任何想法?请在我的案例中使用 –

3

我看不出有任何繁重的工作在那里,除了也许加载图像的方法。

理想情况下,您应该在ui线程上创建所有UI元素(border/grid/etc),并且只能在另一个线程中加载图像。

或者更好的,而不是编程方式创建所有的UI,你应该使用某种形式的ItemsControlDataTemplate生成所有的用户界面,并加载所有的图像异步的某种东西一样视图模型。

+0

+1,但基本上重复了我刚刚在评论中所说的内容。 –

+0

但是,由于“繁重的工作”正在加载图像,使用ItemsControl不会再现冻结UI的相同问题?正在加载的图像不会总是相同的图像数量不会太多,它的所有动态。什么使它更好地使用ItemsControl?将允许我有一个加载屏幕显示和图像被同时添加到ItemsControl?感谢所有提示btw – user3091405

+0

@HighCore y,我发布我的答案,就像你发布你的评论。英雄所见略同! –