WPF中在MVVM模式下实现导航功能

news2025/1/23 22:33:26

WPF中在MVVM模式下实现导航功能

一、利用TabControl

使用场景:项目小,不用考虑内存开销的问题。
splashScreen

实现方式1-手动指定ViewModel

  1. 分别定义3个UserControl作为View用于演示
 <UserControl
     ...>
     <Grid>
         <StackPanel Orientation="Vertical">
             <TextBlock
                 HorizontalAlignment="Center"
                 VerticalAlignment="Top"
                 Text="Page 1" />
             <TextBlock
                 d:Text="Page 1"
                 FontSize="50"
                 Text="{Binding PageMessage}" />
         </StackPanel>
     </Grid>
 </UserControl>
  1. 分别定义ViewModel
 public abstract class PageViewModelBase 
 {
     public string? Header { get; set; }
 }
 public class MainViewModel 
 {
     public List<PageViewModelBase> ViewModels { get; }
 
     public MainViewModel(Page1ViewModel p1, Page2ViewModel p2, Page3ViewModel p3)
     {
         ViewModels = new List<PageViewModelBase> { p1, p2, p3 };
     }
 }
 public class Page1ViewModel : PageViewModelBase
 {
     public Page1ViewModel() => Header = "Page 1";
 
     public string PageMessage { get; set; } = "Hello, Page 1";
 }
 
 public class Page2ViewModel : PageViewModelBase
 {
     public Page2ViewModel() => Header = "Page 2";
 
     public string PageMessage { get; set; } = "Hello, Page 2";
 }
 
 public class Page3ViewModel : PageViewModelBase
 {
     public Page3ViewModel() => Header = "Page 3";
 
     public string PageMessage { get; set; } = "Hello, Page 3";
 }
  1. 在MainWindow上定义Tabcontrol
 <Window
            ...>
     <Grid>
         <TabControl ItemsSource="{Binding ViewModels}">
             <TabItem Header="Pag1">
                 <view:Page1>
                     <view:Page1.DataContext>
                         <local:Page1ViewModel />
                     </view:Page1.DataContext>
                 </view:Page1>
             </TabItem>
             <TabItem Header="Pag2">
                 <view:Page1>
                     <view:Page1.DataContext>
                         <local:Page2ViewModel />
                     </view:Page1.DataContext>
                 </view:Page1>
             </TabItem>
             <TabItem Header="Pag3">
                 <view:Page1>
                     <view:Page1.DataContext>
                         <local:Page3ViewModel />
                     </view:Page1.DataContext>
                 </view:Page1>
             </TabItem>
         </TabControl>
     </Grid>
 </Window>

这种方式需要手动指定每个View的ViewModel

实现方式2-利用ItemTemplate

  1. 在MainViewModel中声明一个ViewModel列表
public class MainViewModel 
{
    public List<PageViewModelBase> ViewModels { get; }

    public MainViewModel(Page1ViewModel p1, Page2ViewModel p2, Page3ViewModel p3)
    {
        ViewModels = new List<PageViewModelBase> { p1, p2, p3 };
    }
}
  1. 在MainWindow中为TabControl指定ItemTemplate,上一步声明的ViewModel列表作为 TabControl 的 ItemsSource;为 TabControl.Resources 添 加多个 DataTemplate,指定 VM 对应什么样的 Page
<Window d:DataContext="{d:DesignInstance Type=local:MainViewModel}"
         ....>
    <Grid>
        <TabControl ItemsSource="{Binding ViewModels}">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Header}"/>
                </DataTemplate>
            </TabControl.ItemTemplate>
            <TabControl.Resources>
                <DataTemplate DataType="{x:Type local:Page1ViewModel}">
                    <view:Page1/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type local:Page2ViewModel}">
                    <view:Page2/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type local:Page3ViewModel}">
                    <view:Page3/>
                </DataTemplate>
            </TabControl.Resources>   
        </TabControl>
    </Grid>
</Window>

这样的好处是自动会为不同的View绑定了相应的ViewModel。

小技巧:在xaml中加上了d:DataContext="{d:DesignInstance Type=local:MainViewModel},这样在写Binding的时候就有了智能提示。

以上两种方式均可结合依赖注入的方式来实现

二、自定义NavigationService服务

splashScreen

  1. 实现一个NavigationService服务,并作为单例
 class NavigationService
 {
 	//设置一个单例服务
     public static NavigationService Instance { get; private set; } = new NavigationService();
     //声明一个事件,当更改CurrentViewModel时触发
     public event Action? CurrentViewModelChanged;
  	//设置一个当前VM的属性,并在属性改变时触发CurrentViewModelChanged
     private ViewModelBase? currentViewModel;
     public ViewModelBase? CurrentViewModel
     {
         get => currentViewModel;
         set
         {
             currentViewModel = value;
             CurrentViewModelChanged?.Invoke();
         }
     }
     //页面导航方法,给CurrentViewModel赋值,触发CurrentViewModelChanged事件
     public void NavigateTo(ViewModelBase viewModel)=>CurrentViewModel = viewModel;
 }
  1. 设置MainViewModel中的CurrentViewModel属性
 public class ViewModelBase : ObservableObject{}
 public partial class MainViewModel : ViewModelBase
 {
     [ObservableProperty]
     private ViewModelBase? currentViewModel;//当前的VM
 
     public MainViewModel()
     {
 		//为事件绑定委托方法,设置CurrentVM和NavigationService中的CurrentVM保持一致
         NavigationService.Instance.CurrentViewModelChanged += () =>
         {
             CurrentViewModel = NavigationService.Instance.CurrentViewModel;
         };
 
         //调用导航方法
         NavigationService.Instance.NavigateTo(new LoginViewModel());
     }
 }

其他两个ViewModel分别为

 public partial class LoginViewModel : ViewModelBase
 {
     [ObservableProperty]
     string? userName = "Sean";

     [RelayCommand]
     void Login()
     {
         NavigationService.Instance.NavigateTo(new HomeViewModel());
     }
 }
 public partial class HomeViewModel : ViewModelBase
 {
     [ObservableProperty]
     string? userName;

     [RelayCommand]
     void Logout()
     {
         NavigationService.Instance.NavigateTo(new LoginViewModel());
     }
 }
  1. 使用ContentControl作为MainWindow上不同页面载体显示内容,并借助DataTemplate来实现View和ViewModel的映射
 <Window ...>
     <ContentControl Content="{Binding CurrentViewModel}">
         <ContentControl.Resources>
             <DataTemplate DataType="{x:Type vm:LoginViewModel}">
                 <view:Login />
             </DataTemplate>
             <DataTemplate DataType="{x:Type vm:HomeViewModel}">
                 <view:Home />
             </DataTemplate>
         </ContentControl.Resources>
     </ContentControl>
 </Window>

在ContentControl.Resources中设置DataTemplate,根据DataType自动选择相应的VM,这样做的好处是会自动将View和VM进行了绑定。

改进

  1. 单例方式可以采用依赖注入的方式来实现
  2. 在NavigationService服务中,可以改进页面导航的方法
public void NavigateTo<T>() where T : ViewModelBase
    => CurrentViewModel = App.Current.Services.GetService<T>();

//在调用导航方法时可以使用
navigationService.NavigateTo<HomeViewModel>();

三、借助ValueConverter

实现上一章节的功能,这种方法本质上是通过View来自动绑定VM。

  1. 定义Page的枚举
 public enum ApplicationPage
 {
     Empty,
     Login,
     Home
 }
  1. 定义各ViewModel
 public class ViewModelBase : ObservableObject{}
 public partial class MainViewModel : ViewModelBase
 {
     //MainViewModel中的CurrentPage是一个枚举类型
     [ObservableProperty]
     ApplicationPage currentPage;
 
     public MainViewModel()
     {
         CurrentPage = ApplicationPage.Login;
     }
 }
 public partial class LoginViewModel : ViewModelBase
 {
     public string UserName { get; set; } = "AngelSix";
 
     [RelayCommand]
     void Login()
     {
         var mainVM= App.Current.MainWindow.DataContext as MainViewModel;
         mainVM!.CurrentPage = ApplicationPage.Home;
     }
 }
 public partial class HomeViewModel : ViewModelBase
 {
     [RelayCommand]
     void Logout()
     {
         var mainVM = App.Current.MainWindow.DataContext as MainViewModel;
         mainVM!.CurrentPage = ApplicationPage.Login;
     }
 }
  1. 定义Page基类和各个Page

    这种方法本质上是通过View来自动绑定VM,所以在此处使用泛型

 public abstract class BasePage<VM> : UserControl where VM : ViewModelBase, new()
 {
     public BasePage()
     {
         DataContext = new VM();
     }
 }
  • 实现Home页面

将Home.xaml.cs中的继承删掉,以为它和Home.xaml相互为分部类,只在一个分部类上实现继承就可以。

 <local:BasePage 
    x:TypeArguments="vm:HomeViewModel"
                 ...>
      <!--x:TypeArguments指定泛型-->
     <Grid>
         <TextBlock HorizontalAlignment="Center"
             VerticalAlignment="Center"
             Text="Home"
             FontSize="32" />
         <Button Margin="10" Grid.Row="1"
          HorizontalAlignment="Right"
          VerticalAlignment="Bottom"
          Content="Logout"
          Command="{Binding LogoutCommand}" />
     </Grid>
 </local:BasePage>
  • 实现Login页面

方法和实现Home页面方法相同

 <local:BasePage
     x:TypeArguments="vm:LoginViewModel" ...>
     <Grid>
         <Border
             Padding="10"
             HorizontalAlignment="Center"
             VerticalAlignment="Center"
             BorderBrush="LightGray"
             BorderThickness="1"
             CornerRadius="10">
             <StackPanel Width="300">
                 <TextBlock HorizontalAlignment="Center" FontSize="28">Login</TextBlock>
                 <Separator Margin="0,10" />
                 <TextBlock>User name:</TextBlock>
                 <TextBox
                     Margin="0,10"
                     InputMethod.IsInputMethodEnabled="False"
                     Text="{Binding UserName}" />
                 <TextBlock>Password:</TextBlock>
                 <PasswordBox Margin="0,10" Password="123456" />
                 <Button Command="{Binding LoginCommand}" Content="Login" />
             </StackPanel>
         </Border>
     </Grid>
 </local:BasePage>
  1. 定义PageViewConverter

    public class PageViewConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            switch ((ApplicationPage)value)
            {
                case ApplicationPage.Empty:
                    return new TextBlock { Text = "404 Not Found" };
                case ApplicationPage.Login:
                    return new Login();
                case ApplicationPage.Home:
                    return new Home();
                default:
                    throw new ArgumentException("Invalid value passed to ApplicationPageViewConverter");
            }
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
  2. 完成MainWindow

 <Window ...>
     <Window.DataContext>
         <local:MainViewModel/>
     </Window.DataContext>
     <Window.Resources>
         <share:PageViewConverter x:Key="pageConv"/>
     </Window.Resources>
     <ContentControl Content="{Binding CurrentPage,Converter={StaticResource pageConv}}"/>
 </Window>

改进

  1. 可以结合依赖注入的方式来实现

  2. 导航方法可以封装为一个NavigationService服务

 //封装服务
 class NavigationService
 {
     public static NavigationService Instance { get; } = new NavigationService();
 
     private MainViewModel mainVM;
 
     public void Navigate(ApplicationPage page)
     {
         if (mainVM == null)
         {
             mainVM = (MainViewModel)App.Current.MainWindow.DataContext;
         }
 
         mainVM.CurrentPage = page;
     }
 }
 //原来的方式
 void Logout()
 {
    var mainVM = App.Current.MainWindow.DataContext as MainViewModel;
    mainVM!.CurrentPage = ApplicationPage.Login;
 }
 //使用封装好的服务
 void Login()
 {
     NavigationService.Instance.Navigate(ApplicationPage.Login);
 }

四、使用Frame和NavigationService

实现上一章节功能,本质上是使用依赖注入的方式将View和ViewModel进行绑定,并利用Frame的自带的Navigate方法进行导航

  1. 定义ViewModel
 public class ViewModelBase : ObservableObject{}
 
 public partial class MainWindowViewModel : ViewModelBase
 {
     private readonly NavigationService navigationService;
     //依赖注入
     public MainWindowViewModel(NavigationService navigationService)
     {
         this.navigationService = navigationService;
     }
 
     [RelayCommand]
     void Loaded()
     {  //navigationService实现的导航方法
         navigationService.Navigate<LoginViewModel>();
     }
 }
 
 public partial class HomeViewModel : ViewModelBase
 {
     [ObservableProperty]
     string? userName;
 }
 
 public partial class LoginViewModel : ViewModelBase
 {
     private readonly NavigationService navigationService;
     //依赖注入
     public string UserName { get; set; } = "Sergio";
 
     public LoginViewModel(NavigationService navigationService)
     {
         this.navigationService = navigationService;
     }
 
     [RelayCommand]
     void Login()
     {   //navigationService实现的导航方法,此处进行了传参
         navigationService.Navigate<HomeViewModel>(new Dictionary<string, object?>
         {
             [nameof(HomeViewModel.UserName)] = UserName
         });
     }
 }
  1. 定义View

主窗口,使用Behaviors实现mvvm模式

<Window
    xmlns:b="http://schemas.microsoft.com/xaml/behaviors">
    <b:Interaction.Triggers>
        <b:EventTrigger>
            <b:InvokeCommandAction Command="{Binding LoadedCommand}" />
        </b:EventTrigger>
    </b:Interaction.Triggers>
</Window>

主窗口后台类

public partial class MainWindow : Window
{
    public MainWindow(MainWindowViewModel viewModel,Frame frame)
    {
        InitializeComponent();
        DataContext = viewModel;
        AddChild(frame);
    }
}

其他View

<!--使用Page来承载内容-->
<Page ...>
    <Grid>
        <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   d:Text="Hello, world!"
                   Text="{Binding UserName, StringFormat='Hello, {0}!'}"
                   FontSize="32" />
    </Grid>
</Page>

<Page ...>
    <Grid>
        <Border Padding="10"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                BorderThickness="1"
                CornerRadius="10"
                BorderBrush="LightGray">
            <StackPanel Width="300">
                <TextBlock HorizontalAlignment="Center" FontSize="28">Login</TextBlock>
                <Separator Margin="0,10" />
                <TextBlock>User name:</TextBlock>
                <TextBox Margin="0,10" Text="{Binding UserName}" InputMethod.IsInputMethodEnabled="False" />
                <TextBlock>Password:</TextBlock>
                <PasswordBox Margin="0,10" Password="123456" />
                <Button Content="Login" Command="{Binding LoginCommand}" />
            </StackPanel>
        </Border>
    </Grid>
</Page>

在后台类中使用依赖注入的方式定义DataContext

public Home(HomeViewModel viewModel)
{
    InitializeComponent();
    DataContext = viewModel;
}

public Login(LoginViewModel viewModel)
{
    InitializeComponent();
    DataContext = viewModel;
}
  1. 实现NavigationService
 public class NavigationService
 {
     //注册了单例的Frame
     private readonly Frame? mainFrame;
 
     public NavigationService(Frame? frame)
     {
         mainFrame = frame;
         //要使用LoadCompleted事件
         mainFrame.LoadCompleted += MainFrame_LoadCompleted;
     }
 
     private void MainFrame_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
     {
         if (e.ExtraData is not Dictionary<string,object?> extraData)
         {
             return;
         }
         if ((mainFrame?.Content as FrameworkElement)?.DataContext is not ViewModelBase vm)
         {
             return;
         }
         foreach (var item in extraData)
         {
             //为每个属性赋值
             vm.GetType().GetProperty(item.Key)?.SetValue(vm, item.Value);
         }
     }
    //根据VM类型查找View,要注意VM和View的命名规范
     private Type? FindView<T>()
     {
         return Assembly.GetAssembly(typeof(T))?.GetTypes().FirstOrDefault(x => x.Name == typeof(T).Name.Replace("ViewModel", ""));
     }
     public void Navigate<T>(Dictionary<string,object?>? extraData=null) where T:ViewModelBase
     {
         var viewType = FindView<T>();
         if (viewType is null)
             return;
         var page = App.Current.Services.GetService(viewType) as Page;
 		//利用Frame的Navigate方法进行导航和传参
         mainFrame?.Navigate(page,extraData);
     }   
 }
  1. 注册需要的类,此案例在App.cs中进行注册
 public partial class App : Application
 {
     public IServiceProvider Services { get; }
 
     public static new App Current => (App)Application.Current;
 
     public App()
     {
         var container = new ServiceCollection();
 
         container.AddSingleton(_ => new Frame { NavigationUIVisibility = NavigationUIVisibility.Hidden });
 
         container.AddSingleton<MainWindow>();
         container.AddSingleton<MainWindowViewModel>();
 
         container.AddTransient<Login>();
         container.AddTransient<Home>();
 
         container.AddTransient<LoginViewModel>();
         container.AddTransient<HomeViewModel>();
 
         container.AddSingleton<NavigationService>();
 
         Services = container.BuildServiceProvider();
     }
 
     protected override void OnStartup(StartupEventArgs e)
     {
         base.OnStartup(e);
 
         MainWindow = Services.GetRequiredService<MainWindow>();
         MainWindow.Show();
     }
 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1101929.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

error: use of undeclared identifier ‘MainWindow

error use of undeclared identifier ‘mainwindow‘ semantic issue in QtCreator在 QtCreator 中错误使用未声明的标识符“_qtcreator 编译无错误,但是提示undeclared identifier_一定不摆烂的研究牲的博客-CSDN博客 我刚刚创建了新的 Qt Widgets 应用程序。我打开 mainwind…

门店没人气?线下实体店如何利用软文推广获客

电商的多样化发展逐步改变人们的消费模式&#xff0c;实体店获客越来越难&#xff0c;流量为王的时代实体店应该如何打响知名度&#xff0c;吸引客户呢&#xff1f;软文推广就是一个不错的方式&#xff0c;帮助商家以低于传统硬广的营销成本获取流量&#xff0c;接下来媒介盒子…

Crypto(1) 攻防世界Caesar

根据题目可以知道是个凯撒加密&#xff0c;准确的说是一个单表代替密码&#xff0c;因为后面可以知道偏移量不是3 一个接着一个试验&#xff1a; 注意加密时偏移量是向后移动&#xff0c;解密时偏移量是向前面移动的 &#xff01;&#xff01;&#xff01;

[NewStarCTF 2023] web题解

文章目录 WEEK1泄漏的秘密Begin of UploadBegin of HTTPErrorFlaskBegin of PHPR!C!E!EasyLogin WEEK2游戏高手include 0。0ez_sqlUnserialize&#xff1f;Upload again!R!!C!!E!! WEEK1 泄漏的秘密 打开题目&#xff0c;提示有敏感信息泄露 直接扫一下目录&#xff0c;发现有…

Verilog编译预处理

文章目录 一、简介二、宏定义define三、文件包含include四、时间尺度timescale五、条件编译ifdef参考 一、简介 编译预处理是Verilog HDL编译系统的一个组成部分。 Verilog HDL编译系统通常先对这些特殊的命令进行预处理&#xff0c;然后将预处理的结果和源程序一起在进行通常…

Python —— hou.Node class

Houdini内所有节点&#xff08;Object、SOP、COP等&#xff09;的基类&#xff0c;该类的实例对应houdini内的节点&#xff1b; 每个节点都有一个唯一的路径&#xff08;定义其在节点树内的位置&#xff09;&#xff1b;节点路径层次结构类似于文件系统中的文件和文件夹的层次结…

ant design DatePicker禁用之前的时间

1、代码 <DatePicker fieldProps{disabledDate: (current: any) > {return current < moment().startOf(day);}}/>2、效果

正点原子嵌入式linux驱动开发——Linux设备树

在前面系统移植的过程中多次提到“设备树”这个概念和创建自己的设备树。但是并没有在TF-A和uboot里说设备树的原理&#xff0c;因为相对比较复杂。 本章就来详细的谈一谈设备树。掌握设备树是Linux驱动开发人员必备的技能&#xff01;因为在新版本的Linux中&#xff0c;ARM相…

icg模块clock gating解析

// 两种形式&#xff1a; 与门形式 或门形式 三个用途&#xff1a; 用于关断时钟&#xff0c;降低功耗用于动态时钟切换是防止毛刺的产生用于时钟分频 解析&#xff1a;与门形式 解析&#xff1a;或门形式

Mybatis对数据库进行增删查改以及单元测试

这篇写的草率了&#xff0c;是好几天前学到&#xff0c;以后用来自己复习 UserInfo import lombok.Data;Data public class UserInfo {private int id;private String name;private int age;private String email;//LocalDateTime可用于接收 时间}Mapper UserMapper pack…

如何获取方法上声明的注解

开发过程当中部分场景需要获取到方法或类上的注解&#xff0c;但是经常会有无法渠道注解的情况&#xff0c;这种情况很可能是该注解所在的类是一个代理类&#xff0c;比如被AOP动态代理【注意&#xff1a;通过】。本文以CGLIB为例&#xff0c;说明为什么无法取到方法或类上的注…

读《中国省级移动政务服务报告2023》

报告地址&#xff1a; 中国省级移动政务服务报告2022 https://www.digitalelite.cn/h-nd-7846.html 中国省级移动政务服务报告2023 中国省级移动政务服务报告2023 报告分为 引言、评估方法、概貌、指数、标杆、建言 六个部分。 一些思考 移动政务服务应用针对各省的常驻人口…

vueday02——使用NTableData

1.下载naivueui 2.按需导入&#xff0c;不要全局导入 注意不要导入错误组件或者写错组件名称 import { NDataTable } from naive-ui 3.定义表头和数据&#xff01;&#xff01;&#xff01; n-data-table标签必须要使用数据和数据 少一个都不能正确渲染&#xff01;&#xf…

在 Android 上恢复已删除音乐的 5 种简单方法

人们经常将重要的音乐文件保存在智能手机上&#xff0c;以方便随时随地收听自己喜欢的曲目。但是&#xff0c;如果这些珍贵的音乐文件因软件故障或硬件故障而被意外删除或丢失怎么办&#xff1f;这将是许多音乐爱好者的噩梦&#xff01; 如果您也是这些人中的一员&#xff0c;…

8月PMP出成绩了,意味着什么?

之前胖圆给大家分享过&#xff0c;10月16号PMI官网将发布8.19的PMP考试成绩&#xff0c;这两天已经有学员陆陆续续收到考试通过的邮件&#xff0c;还没有收到成绩通知的学员耐心等待&#xff0c;相信努力备考的学员一定都能够顺利通过~ 还没查询成绩的宝子可以移步胖圆上一篇笔…

C/C++面试常见问题——static关键字的主要用法

首先我们要明确一下C/C的内存区域划分 在C/C中内存主要被划分为四大块&#xff0c;堆&#xff0c;栈&#xff0c;全局/静态存储区&#xff0c;代码区 而全局/静态存储区又被细分为常量区(静态常量区&#xff0c;const关键字修饰)&#xff0c;全局区(全局变量区)和静态变量区(…

LiveGBS流媒体平台GB/T28181常见问题-安全控制HTTP接口鉴权勾选流地址鉴权后401Unauthorized如何播放调用接口

LiveGBS流媒体平台GB/T28181常见问题-安全控制HTTP接口鉴权勾选流地址鉴权后401 Unauthorized如何播放调用接口&#xff1f; 1、安全控制1.1、HTTP接口鉴权1.2、流地址鉴权 2、401 Unauthorized2.1、携带token调用接口2.1.1、获取鉴权token2.1.2、调用其它接口2.1.2.1、携带 Co…

微盟盈利困局难解

又是一年“双11”临近&#xff0c;微盟(2013.HK)、有赞等国内电商SaaS头部企业之间的战斗已经悄然打响。 作为从微信生态里生长出来的第三方服务商&#xff0c;根据微盟此前公布2023年半年报&#xff0c;其营收增长背后仍难掩亏损困局。 「不二研究」据微盟财报发现&#xff…

新产品发布新闻稿推广文案怎么写?纯干货

一篇优质的新闻稿传播速度是非常快的&#xff0c;可以让产品获得大量曝光和展现&#xff0c;提高产品的知名度和口碑&#xff0c;这样的稿件撰写起来是需要掌握一定的技巧的&#xff0c;新产品发布新闻稿推广文案怎么写&#xff1f;伯乐网络传媒十多年文案撰写经验&#xff0c;…

Windows10系统开启SNMP服务

Windows10系统开启SNMP服务 1、打开控制面板&#xff0c;选择卸载程序2、点击启用或关闭Windows功能3、将SNMP协议开启&#xff08;如果有该内容请直接跳至步骤7&#xff0c;如找不到该协议请继续看步骤4&#xff09;4、找不到步骤3中内容原因&#xff1a;Windwos10 1809后更新…